Minor paint improvements. Lazy loading of libraries.
This commit is contained in:
parent
b5b4091f7f
commit
3c7b93bf7a
960 changed files with 41812 additions and 11015 deletions
8
Makefile
8
Makefile
|
|
@ -231,7 +231,7 @@ deploy-sdk: tools apps installer
|
||||||
@echo "Building SDK..."
|
@echo "Building SDK..."
|
||||||
@# Start from an empty tree so a header dropped from the lists goes away.
|
@# Start from an empty tree so a header dropped from the lists goes away.
|
||||||
rm -rf $(SDKDIR)
|
rm -rf $(SDKDIR)
|
||||||
@mkdir -p $(SDKDIR)/bin $(SDKDIR)/include/core $(SDKDIR)/include/shell $(SDKDIR)/include/tasks $(SDKDIR)/include/sql $(SDKDIR)/include/texthelp $(SDKDIR)/include/listhelp $(SDKDIR)/include/richtext $(SDKDIR)/include/fileops $(SDKDIR)/include/hlpfile $(SDKDIR)/include/xfer $(SDKDIR)/include/conn $(SDKDIR)/include/print $(SDKDIR)/include/game $(SDKDIR)/include/joystick $(SDKDIR)/include/audio $(SDKDIR)/include/music $(SDKDIR)/include/pkg $(SDKDIR)/include/json $(SDKDIR)/include/http $(SDKDIR)/include/pkgnet $(SDKDIR)/include/net $(SDKDIR)/include/basic $(SDKDIR)/samples/basic/basdemo $(SDKDIR)/samples/basic/widshow
|
@mkdir -p $(SDKDIR)/bin $(SDKDIR)/include/core $(SDKDIR)/include/shell $(SDKDIR)/include/tasks $(SDKDIR)/include/sql $(SDKDIR)/include/serial $(SDKDIR)/include/crypto $(SDKDIR)/include/seclink $(SDKDIR)/include/texthelp $(SDKDIR)/include/listhelp $(SDKDIR)/include/richtext $(SDKDIR)/include/fileops $(SDKDIR)/include/hlpfile $(SDKDIR)/include/xfer $(SDKDIR)/include/conn $(SDKDIR)/include/print $(SDKDIR)/include/game $(SDKDIR)/include/joystick $(SDKDIR)/include/audio $(SDKDIR)/include/music $(SDKDIR)/include/pkg $(SDKDIR)/include/json $(SDKDIR)/include/http $(SDKDIR)/include/pkgnet $(SDKDIR)/include/net $(SDKDIR)/include/basic $(SDKDIR)/samples/basic/basdemo $(SDKDIR)/samples/basic/widshow
|
||||||
cp $(SDK_CORE_H) $(SDKDIR)/include/core/
|
cp $(SDK_CORE_H) $(SDKDIR)/include/core/
|
||||||
cp $(SDK_SHELL_H) $(SDKDIR)/include/shell/
|
cp $(SDK_SHELL_H) $(SDKDIR)/include/shell/
|
||||||
cp $(SDK_TASKS_H) $(SDKDIR)/include/tasks/
|
cp $(SDK_TASKS_H) $(SDKDIR)/include/tasks/
|
||||||
|
|
@ -259,9 +259,9 @@ deploy-sdk: tools apps installer
|
||||||
@for h in $(SDK_NET_STD_H); do \
|
@for h in $(SDK_NET_STD_H); do \
|
||||||
mkdir -p $(SDKDIR)/include/net/$$(dirname $$h) && cp $(SDK_NET_DIR)/$$h $(SDKDIR)/include/net/$$h || exit 1; \
|
mkdir -p $(SDKDIR)/include/net/$$(dirname $$h) && cp $(SDK_NET_DIR)/$$h $(SDKDIR)/include/net/$$h || exit 1; \
|
||||||
done
|
done
|
||||||
@for h in $(SDK_SERIAL_H); do \
|
cp $(SDK_SERIAL_H) $(SDKDIR)/include/serial/
|
||||||
mkdir -p $(SDKDIR)/include/serial/$$(dirname $$h) && cp $(SDK_SERIAL_DIR)/$$h $(SDKDIR)/include/serial/$$h || exit 1; \
|
cp $(SDK_CRYPTO_H) $(SDKDIR)/include/crypto/
|
||||||
done
|
cp $(SDK_SECLINK_H) $(SDKDIR)/include/seclink/
|
||||||
@# Widget headers -- one subdir per widget
|
@# Widget headers -- one subdir per widget
|
||||||
@for h in $(SDK_WIDGET_H); do \
|
@for h in $(SDK_WIDGET_H); do \
|
||||||
wgt=$$(basename $$(dirname $$h)); \
|
wgt=$$(basename $$(dirname $$h)); \
|
||||||
|
|
|
||||||
22
analyze.sh
22
analyze.sh
|
|
@ -1,4 +1,26 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
# The MIT License (MIT)
|
||||||
|
#
|
||||||
|
# Copyright (C) 2026 Scott Duensing
|
||||||
|
#
|
||||||
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
# of this software and associated documentation files (the "Software"), to
|
||||||
|
# deal in the Software without restriction, including without limitation the
|
||||||
|
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||||
|
# sell copies of the Software, and to permit persons to whom the Software is
|
||||||
|
# furnished to do so, subject to the following conditions:
|
||||||
|
#
|
||||||
|
# The above copyright notice and this permission notice shall be included in
|
||||||
|
# all copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||||
|
# IN THE SOFTWARE.
|
||||||
|
|
||||||
#
|
#
|
||||||
# analyze.sh -- run the DJGPP build under gcc's -fanalyzer.
|
# analyze.sh -- run the DJGPP build under gcc's -fanalyzer.
|
||||||
#
|
#
|
||||||
|
|
|
||||||
|
|
@ -178,6 +178,8 @@ ignored. Names are case-insensitive.
|
||||||
| `dvxshell.dep` | dvxshell.lib | libtasks, libdvx, texthelp, listhelp |
|
| `dvxshell.dep` | dvxshell.lib | libtasks, libdvx, texthelp, listhelp |
|
||||||
| `basrt.dep` | basrt.lib | libtasks, libdvx |
|
| `basrt.dep` | basrt.lib | libtasks, libdvx |
|
||||||
| `serial.dep` | serial.lib | libtasks, libdvx |
|
| `serial.dep` | serial.lib | libtasks, libdvx |
|
||||||
|
| `crypto.dep` | crypto.lib | libdvx |
|
||||||
|
| `seclink.dep` | seclink.lib | crypto, serial, libdvx |
|
||||||
|
|
||||||
### Widget Dependencies
|
### Widget Dependencies
|
||||||
|
|
||||||
|
|
@ -186,7 +188,7 @@ ignored. Names are case-insensitive.
|
||||||
| `textinpt.dep` | textinpt.wgt | texthelp |
|
| `textinpt.dep` | textinpt.wgt | texthelp |
|
||||||
| `combobox.dep` | combobox.wgt | texthelp, listhelp |
|
| `combobox.dep` | combobox.wgt | texthelp, listhelp |
|
||||||
| `spinner.dep` | spinner.wgt | texthelp |
|
| `spinner.dep` | spinner.wgt | texthelp |
|
||||||
| `terminal.dep` | terminal.wgt | texthelp |
|
| `ansiterm.dep` | ansiterm.wgt | texthelp |
|
||||||
| `dropdown.dep` | dropdown.wgt | listhelp |
|
| `dropdown.dep` | dropdown.wgt | listhelp |
|
||||||
| `listbox.dep` | listbox.wgt | listhelp |
|
| `listbox.dep` | listbox.wgt | listhelp |
|
||||||
| `listview.dep` | listview.wgt | listhelp |
|
| `listview.dep` | listview.wgt | listhelp |
|
||||||
|
|
|
||||||
24
cppcheck.sh
24
cppcheck.sh
|
|
@ -1,4 +1,26 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
# The MIT License (MIT)
|
||||||
|
#
|
||||||
|
# Copyright (C) 2026 Scott Duensing
|
||||||
|
#
|
||||||
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
# of this software and associated documentation files (the "Software"), to
|
||||||
|
# deal in the Software without restriction, including without limitation the
|
||||||
|
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||||
|
# sell copies of the Software, and to permit persons to whom the Software is
|
||||||
|
# furnished to do so, subject to the following conditions:
|
||||||
|
#
|
||||||
|
# The above copyright notice and this permission notice shall be included in
|
||||||
|
# all copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||||
|
# IN THE SOFTWARE.
|
||||||
|
|
||||||
#
|
#
|
||||||
# cppcheck.sh -- static analysis for the DVX source tree.
|
# cppcheck.sh -- static analysis for the DVX source tree.
|
||||||
#
|
#
|
||||||
|
|
@ -28,6 +50,8 @@ INCLUDES=(
|
||||||
-Isrc/libs/kpunch/dvxshell
|
-Isrc/libs/kpunch/dvxshell
|
||||||
-Isrc/libs/kpunch/taskmgr
|
-Isrc/libs/kpunch/taskmgr
|
||||||
-Isrc/libs/kpunch/serial
|
-Isrc/libs/kpunch/serial
|
||||||
|
-Isrc/libs/kpunch/crypto
|
||||||
|
-Isrc/libs/kpunch/seclink
|
||||||
-Isrc/widgets/kpunch
|
-Isrc/widgets/kpunch
|
||||||
-Isrc/apps/kpunch/dvxbasic
|
-Isrc/apps/kpunch/dvxbasic
|
||||||
-Isrc/apps/kpunch/dvxbasic/compiler
|
-Isrc/apps/kpunch/dvxbasic/compiler
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -1,3 +1,25 @@
|
||||||
|
# The MIT License (MIT)
|
||||||
|
#
|
||||||
|
# Copyright (C) 2026 Scott Duensing
|
||||||
|
#
|
||||||
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
# of this software and associated documentation files (the "Software"), to
|
||||||
|
# deal in the Software without restriction, including without limitation the
|
||||||
|
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||||
|
# sell copies of the Software, and to permit persons to whom the Software is
|
||||||
|
# furnished to do so, subject to the following conditions:
|
||||||
|
#
|
||||||
|
# The above copyright notice and this permission notice shall be included in
|
||||||
|
# all copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||||
|
# IN THE SOFTWARE.
|
||||||
|
|
||||||
[sdl]
|
[sdl]
|
||||||
windowresolution = 1024x768
|
windowresolution = 1024x768
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,25 @@
|
||||||
|
# The MIT License (MIT)
|
||||||
|
#
|
||||||
|
# Copyright (C) 2026 Scott Duensing
|
||||||
|
#
|
||||||
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
# of this software and associated documentation files (the "Software"), to
|
||||||
|
# deal in the Software without restriction, including without limitation the
|
||||||
|
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||||
|
# sell copies of the Software, and to permit persons to whom the Software is
|
||||||
|
# furnished to do so, subject to the following conditions:
|
||||||
|
#
|
||||||
|
# The above copyright notice and this permission notice shall be included in
|
||||||
|
# all copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||||
|
# IN THE SOFTWARE.
|
||||||
|
|
||||||
[sdl]
|
[sdl]
|
||||||
output = surface
|
output = surface
|
||||||
windowresolution = 1024x768
|
windowresolution = 1024x768
|
||||||
|
|
|
||||||
BIN
screenshots/86box/parpm-parpm.png
(Stored with Git LFS)
BIN
screenshots/86box/parpm-parpm.png
(Stored with Git LFS)
Binary file not shown.
BIN
screenshots/86box/pkgsign-ps-plan.png
(Stored with Git LFS)
Normal file
BIN
screenshots/86box/pkgsign-ps-plan.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
screenshots/86box/upgdrop-ud-sw.png
(Stored with Git LFS)
BIN
screenshots/86box/upgdrop-ud-sw.png
(Stored with Git LFS)
Binary file not shown.
BIN
screenshots/dosbox/cpanel-cp-types.png
(Stored with Git LFS)
BIN
screenshots/dosbox/cpanel-cp-types.png
(Stored with Git LFS)
Binary file not shown.
BIN
screenshots/dosbox/hang-ended.png
(Stored with Git LFS)
BIN
screenshots/dosbox/hang-ended.png
(Stored with Git LFS)
Binary file not shown.
BIN
screenshots/dosbox/hang-taskmgr.png
(Stored with Git LFS)
BIN
screenshots/dosbox/hang-taskmgr.png
(Stored with Git LFS)
Binary file not shown.
BIN
screenshots/dosbox/instadd-add-sw.png
(Stored with Git LFS)
BIN
screenshots/dosbox/instadd-add-sw.png
(Stored with Git LFS)
Binary file not shown.
BIN
screenshots/dosbox/instadd-su-what.png
(Stored with Git LFS)
BIN
screenshots/dosbox/instadd-su-what.png
(Stored with Git LFS)
Binary file not shown.
BIN
screenshots/dosbox/menus-m-design.png
(Stored with Git LFS)
BIN
screenshots/dosbox/menus-m-design.png
(Stored with Git LFS)
Binary file not shown.
BIN
screenshots/dosbox/parpm-parpm.png
(Stored with Git LFS)
BIN
screenshots/dosbox/parpm-parpm.png
(Stored with Git LFS)
Binary file not shown.
BIN
screenshots/dosbox/pkgsign-ps-plan.png
(Stored with Git LFS)
Normal file
BIN
screenshots/dosbox/pkgsign-ps-plan.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
screenshots/dosbox/player-p-wav.png
(Stored with Git LFS)
BIN
screenshots/dosbox/player-p-wav.png
(Stored with Git LFS)
Binary file not shown.
BIN
screenshots/dosbox/sdk-sdktbx.png
(Stored with Git LFS)
BIN
screenshots/dosbox/sdk-sdktbx.png
(Stored with Git LFS)
Binary file not shown.
BIN
screenshots/dosbox/shellcmd-sc-back.png
(Stored with Git LFS)
Normal file
BIN
screenshots/dosbox/shellcmd-sc-back.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
screenshots/dosbox/sql-sqlsmk.png
(Stored with Git LFS)
BIN
screenshots/dosbox/sql-sqlsmk.png
(Stored with Git LFS)
Binary file not shown.
BIN
screenshots/dosbox/ucide-ucide-ds.png
(Stored with Git LFS)
BIN
screenshots/dosbox/ucide-ucide-ds.png
(Stored with Git LFS)
Binary file not shown.
BIN
screenshots/dosbox/ucide-ucide-tb.png
(Stored with Git LFS)
BIN
screenshots/dosbox/ucide-ucide-tb.png
(Stored with Git LFS)
Binary file not shown.
BIN
screenshots/dosbox/upgdrop-ud-sw.png
(Stored with Git LFS)
BIN
screenshots/dosbox/upgdrop-ud-sw.png
(Stored with Git LFS)
Binary file not shown.
|
|
@ -1,3 +1,25 @@
|
||||||
|
-- The MIT License (MIT)
|
||||||
|
--
|
||||||
|
-- Copyright (C) 2026 Scott Duensing
|
||||||
|
--
|
||||||
|
-- Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
-- of this software and associated documentation files (the "Software"), to
|
||||||
|
-- deal in the Software without restriction, including without limitation the
|
||||||
|
-- rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||||
|
-- sell copies of the Software, and to permit persons to whom the Software is
|
||||||
|
-- furnished to do so, subject to the following conditions:
|
||||||
|
--
|
||||||
|
-- The above copyright notice and this permission notice shall be included in
|
||||||
|
-- all copies or substantial portions of the Software.
|
||||||
|
--
|
||||||
|
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
-- FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||||
|
-- IN THE SOFTWARE.
|
||||||
|
|
||||||
-- dvxserver.lua -- the DVX package server: the catalogue of add-on packages that DVX's Software
|
-- dvxserver.lua -- the DVX package server: the catalogue of add-on packages that DVX's Software
|
||||||
-- program browses, installs, and updates from, and where publishers upload them.
|
-- program browses, installs, and updates from, and where publishers upload them.
|
||||||
--
|
--
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,25 @@
|
||||||
|
-- The MIT License (MIT)
|
||||||
|
--
|
||||||
|
-- Copyright (C) 2026 Scott Duensing
|
||||||
|
--
|
||||||
|
-- Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
-- of this software and associated documentation files (the "Software"), to
|
||||||
|
-- deal in the Software without restriction, including without limitation the
|
||||||
|
-- rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||||
|
-- sell copies of the Software, and to permit persons to whom the Software is
|
||||||
|
-- furnished to do so, subject to the following conditions:
|
||||||
|
--
|
||||||
|
-- The above copyright notice and this permission notice shall be included in
|
||||||
|
-- all copies or substantial portions of the Software.
|
||||||
|
--
|
||||||
|
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
-- FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||||
|
-- IN THE SOFTWARE.
|
||||||
|
|
||||||
-- packages.lua -- the DVX package catalogue: packages, their versions, what each needs, and the
|
-- packages.lua -- the DVX package catalogue: packages, their versions, what each needs, and the
|
||||||
-- review that publishes a version.
|
-- review that publishes a version.
|
||||||
--
|
--
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,25 @@
|
||||||
|
-- The MIT License (MIT)
|
||||||
|
--
|
||||||
|
-- Copyright (C) 2026 Scott Duensing
|
||||||
|
--
|
||||||
|
-- Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
-- of this software and associated documentation files (the "Software"), to
|
||||||
|
-- deal in the Software without restriction, including without limitation the
|
||||||
|
-- rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||||
|
-- sell copies of the Software, and to permit persons to whom the Software is
|
||||||
|
-- furnished to do so, subject to the following conditions:
|
||||||
|
--
|
||||||
|
-- The above copyright notice and this permission notice shall be included in
|
||||||
|
-- all copies or substantial portions of the Software.
|
||||||
|
--
|
||||||
|
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
-- FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||||
|
-- IN THE SOFTWARE.
|
||||||
|
|
||||||
-- account.lua -- an account on the service under test, made as a person makes one: registered, the
|
-- account.lua -- an account on the service under test, made as a person makes one: registered, the
|
||||||
-- link in its mail followed (read from the mail queue in the database), then signed in. Loaded by
|
-- link in its mail followed (read from the mail queue in the database), then signed in. Loaded by
|
||||||
-- the suites, and run by serve.sh for the DOS tests' account:
|
-- the suites, and run by serve.sh for the DOS tests' account:
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,26 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
# The MIT License (MIT)
|
||||||
|
#
|
||||||
|
# Copyright (C) 2026 Scott Duensing
|
||||||
|
#
|
||||||
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
# of this software and associated documentation files (the "Software"), to
|
||||||
|
# deal in the Software without restriction, including without limitation the
|
||||||
|
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||||
|
# sell copies of the Software, and to permit persons to whom the Software is
|
||||||
|
# furnished to do so, subject to the following conditions:
|
||||||
|
#
|
||||||
|
# The above copyright notice and this permission notice shall be included in
|
||||||
|
# all copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||||
|
# IN THE SOFTWARE.
|
||||||
|
|
||||||
# run.sh -- the DVX package server's tests, as singemaster's are run: fixtures made, the server
|
# run.sh -- the DVX package server's tests, as singemaster's are run: fixtures made, the server
|
||||||
# started on a port of its own with a configuration of its own, the suites run against it, the
|
# started on a port of its own with a configuration of its own, the suites run against it, the
|
||||||
# server stopped. Then both services in one calog process, each answering on its own port.
|
# server stopped. Then both services in one calog process, each answering on its own port.
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,26 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
# The MIT License (MIT)
|
||||||
|
#
|
||||||
|
# Copyright (C) 2026 Scott Duensing
|
||||||
|
#
|
||||||
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
# of this software and associated documentation files (the "Software"), to
|
||||||
|
# deal in the Software without restriction, including without limitation the
|
||||||
|
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||||
|
# sell copies of the Software, and to permit persons to whom the Software is
|
||||||
|
# furnished to do so, subject to the following conditions:
|
||||||
|
#
|
||||||
|
# The above copyright notice and this permission notice shall be included in
|
||||||
|
# all copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||||
|
# IN THE SOFTWARE.
|
||||||
|
|
||||||
# serve.sh DATA PORT FIXTURES DVXPKG USER PASSWORD -- the package server for the DOS tests (runDos.sh starts it
|
# serve.sh DATA PORT FIXTURES DVXPKG USER PASSWORD -- the package server for the DOS tests (runDos.sh starts it
|
||||||
# for a "# REQUIRES: PKGSERVER" script and stops it by its PID). In DATA, from nothing:
|
# for a "# REQUIRES: PKGSERVER" script and stops it by its PID). In DATA, from nothing:
|
||||||
#
|
#
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,25 @@
|
||||||
|
-- The MIT License (MIT)
|
||||||
|
--
|
||||||
|
-- Copyright (C) 2026 Scott Duensing
|
||||||
|
--
|
||||||
|
-- Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
-- of this software and associated documentation files (the "Software"), to
|
||||||
|
-- deal in the Software without restriction, including without limitation the
|
||||||
|
-- rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||||
|
-- sell copies of the Software, and to permit persons to whom the Software is
|
||||||
|
-- furnished to do so, subject to the following conditions:
|
||||||
|
--
|
||||||
|
-- The above copyright notice and this permission notice shall be included in
|
||||||
|
-- all copies or substantial portions of the Software.
|
||||||
|
--
|
||||||
|
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
-- FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||||
|
-- IN THE SOFTWARE.
|
||||||
|
|
||||||
-- testPackages.lua -- the package server end to end: uploads checked by the package tool and
|
-- testPackages.lua -- the package server end to end: uploads checked by the package tool and
|
||||||
-- refused for the right reasons, review with the repository's signature (made here, as on the
|
-- refused for the right reasons, review with the repository's signature (made here, as on the
|
||||||
-- operator's own computer), the public catalogue, downloads (resumed) that check against the
|
-- operator's own computer), the public catalogue, downloads (resumed) that check against the
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,25 @@
|
||||||
|
-- The MIT License (MIT)
|
||||||
|
--
|
||||||
|
-- Copyright (C) 2026 Scott Duensing
|
||||||
|
--
|
||||||
|
-- Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
-- of this software and associated documentation files (the "Software"), to
|
||||||
|
-- deal in the Software without restriction, including without limitation the
|
||||||
|
-- rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||||
|
-- sell copies of the Software, and to permit persons to whom the Software is
|
||||||
|
-- furnished to do so, subject to the following conditions:
|
||||||
|
--
|
||||||
|
-- The above copyright notice and this permission notice shall be included in
|
||||||
|
-- all copies or substantial portions of the Software.
|
||||||
|
--
|
||||||
|
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
-- FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||||
|
-- IN THE SOFTWARE.
|
||||||
|
|
||||||
-- testReview.lua -- the operator's review tool (tools/review.lua) against the running service: what
|
-- testReview.lua -- the operator's review tool (tools/review.lua) against the running service: what
|
||||||
-- waits, a fetch and the package tool's word on it, an approval signed with the repository's key
|
-- waits, a fetch and the package tool's word on it, an approval signed with the repository's key
|
||||||
-- that DVX would trust, a rejection, withdrawing and publishing again, and a fetched file changed
|
-- that DVX would trust, a rejection, withdrawing and publishing again, and a fetched file changed
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,25 @@
|
||||||
|
-- The MIT License (MIT)
|
||||||
|
--
|
||||||
|
-- Copyright (C) 2026 Scott Duensing
|
||||||
|
--
|
||||||
|
-- Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
-- of this software and associated documentation files (the "Software"), to
|
||||||
|
-- deal in the Software without restriction, including without limitation the
|
||||||
|
-- rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||||
|
-- sell copies of the Software, and to permit persons to whom the Software is
|
||||||
|
-- furnished to do so, subject to the following conditions:
|
||||||
|
--
|
||||||
|
-- The above copyright notice and this permission notice shall be included in
|
||||||
|
-- all copies or substantial portions of the Software.
|
||||||
|
--
|
||||||
|
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
-- FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||||
|
-- IN THE SOFTWARE.
|
||||||
|
|
||||||
-- review.lua -- the operator's review of uploaded packages, run on the operator's own computer: the
|
-- review.lua -- the operator's review of uploaded packages, run on the operator's own computer: the
|
||||||
-- one place the repository's secret key is kept. It lists what waits, fetches a version and shows
|
-- one place the repository's secret key is kept. It lists what waits, fetches a version and shows
|
||||||
-- what it is (the package tool's own check of it), approves a version with the repository's
|
-- what it is (the package tool's own check of it), approves a version with the repository's
|
||||||
|
|
|
||||||
|
|
@ -1,24 +1,24 @@
|
||||||
// The MIT License (MIT)
|
# The MIT License (MIT)
|
||||||
//
|
#
|
||||||
// Copyright (C) 2026 Scott Duensing
|
# Copyright (C) 2026 Scott Duensing
|
||||||
//
|
#
|
||||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
// of this software and associated documentation files (the "Software"), to
|
# of this software and associated documentation files (the "Software"), to
|
||||||
// deal in the Software without restriction, including without limitation the
|
# deal in the Software without restriction, including without limitation the
|
||||||
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||||
// sell copies of the Software, and to permit persons to whom the Software is
|
# sell copies of the Software, and to permit persons to whom the Software is
|
||||||
// furnished to do so, subject to the following conditions:
|
# furnished to do so, subject to the following conditions:
|
||||||
//
|
#
|
||||||
// The above copyright notice and this permission notice shall be included in
|
# The above copyright notice and this permission notice shall be included in
|
||||||
// all copies or substantial portions of the Software.
|
# all copies or substantial portions of the Software.
|
||||||
//
|
#
|
||||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||||
// IN THE SOFTWARE.
|
# IN THE SOFTWARE.
|
||||||
|
|
||||||
# BASIC Demo help. Compiled to basdemo.hlp next to basdemo.app; F1 and
|
# BASIC Demo help. Compiled to basdemo.hlp next to basdemo.app; F1 and
|
||||||
# Help > Contents in the BASIC Demo open it.
|
# Help > Contents in the BASIC Demo open it.
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,30 @@
|
||||||
VERSION DVX 1.00 CLASS
|
VERSION DVX 1.00 CLASS
|
||||||
Attribute VB_Name = "Shape"
|
Attribute VB_Name = "Shape"
|
||||||
|
|
||||||
|
|
||||||
|
' The MIT License (MIT)
|
||||||
|
'
|
||||||
|
' Copyright (C) 2026 Scott Duensing
|
||||||
|
'
|
||||||
|
' Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
' of this software and associated documentation files (the "Software"), to
|
||||||
|
' deal in the Software without restriction, including without limitation the
|
||||||
|
' rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||||
|
' sell copies of the Software, and to permit persons to whom the Software is
|
||||||
|
' furnished to do so, subject to the following conditions:
|
||||||
|
'
|
||||||
|
' The above copyright notice and this permission notice shall be included in
|
||||||
|
' all copies or substantial portions of the Software.
|
||||||
|
'
|
||||||
|
' THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
' IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
' FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
' AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
' LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
' FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||||
|
' IN THE SOFTWARE.
|
||||||
|
|
||||||
|
|
||||||
' A shape for the Objects demo: its kind and size, and the area that
|
' A shape for the Objects demo: its kind and size, and the area that
|
||||||
' follows from them. Every new Shape starts as a square of size 1.
|
' follows from them. Every new Shape starts as a square of size 1.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -233,8 +233,8 @@ static int32_t sChoice[SoundCountE];
|
||||||
// Prototypes
|
// Prototypes
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
static void addBankRow(WidgetT *page, const char *label, BankRowT *row, PrefsHandleT *prefs);
|
static void addBankRow(WidgetT *page, const char *label, BankRowT *row, PrefsHandleT *prefs, char **banks, const PkgDbT *db);
|
||||||
static int32_t addBanks(BankRowT *row, PrefsHandleT *prefs);
|
static int32_t addBanks(BankRowT *row, PrefsHandleT *prefs, char **banks, const PkgDbT *db);
|
||||||
static int32_t addDriver(const char *name, const char *label);
|
static int32_t addDriver(const char *name, const char *label);
|
||||||
static int32_t addFile(const char *path);
|
static int32_t addFile(const char *path);
|
||||||
static void addFound(BankRowT *row, BankFindFnT *find, const char *label);
|
static void addFound(BankRowT *row, BankFindFnT *find, const char *label);
|
||||||
|
|
@ -263,9 +263,10 @@ static void showVolume(void);
|
||||||
|
|
||||||
|
|
||||||
// A row of banks (its dropdown, and its Test when the music library is
|
// A row of banks (its dropdown, and its Test when the music library is
|
||||||
// there).
|
// there), from the banks folders' files (sorted) and the package records
|
||||||
static void addBankRow(WidgetT *page, const char *label, BankRowT *row, PrefsHandleT *prefs) {
|
// (NULL: none).
|
||||||
int32_t sel = addBanks(row, prefs);
|
static void addBankRow(WidgetT *page, const char *label, BankRowT *row, PrefsHandleT *prefs, char **banks, const PkgDbT *db) {
|
||||||
|
int32_t sel = addBanks(row, prefs, banks, db);
|
||||||
WidgetT *box = addRow(page, label);
|
WidgetT *box = addRow(page, label);
|
||||||
|
|
||||||
row->drop = wgtDropdown(box);
|
row->drop = wgtDropdown(box);
|
||||||
|
|
@ -283,26 +284,20 @@ static void addBankRow(WidgetT *page, const char *label, BankRowT *row, PrefsHan
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// A row's banks: the standard one, those of its kind in the banks folders
|
// A row's banks: the standard one, those of its kind among banks (the
|
||||||
// (by the names DVX.INI keeps, shown by bankLabel; the standard one not
|
// banks folders' files, by the names DVX.INI keeps, shown by bankLabel;
|
||||||
// twice), the sound cards' own banks where the row offers them, and
|
// the standard one not twice), the sound cards' own banks where the row
|
||||||
// DVX.INI's own choice when it is none of them (so OK keeps it). Returns
|
// offers them, and DVX.INI's own choice when it is none of them (so OK
|
||||||
// DVX.INI's choice's item.
|
// keeps it). Returns DVX.INI's choice's item.
|
||||||
static int32_t addBanks(BankRowT *row, PrefsHandleT *prefs) {
|
static int32_t addBanks(BankRowT *row, PrefsHandleT *prefs, char **banks, const PkgDbT *db) {
|
||||||
char **names = platformKindReadDir(DvxKindBanksE);
|
|
||||||
PkgDbT db;
|
|
||||||
bool haveDb = sPkg.load && sPkg.load(&db, "");
|
|
||||||
int32_t sel;
|
|
||||||
|
|
||||||
choiceAdd(&row->choices, "", CPS_BANK_LABEL);
|
choiceAdd(&row->choices, "", CPS_BANK_LABEL);
|
||||||
qsort(names, arrlen(names), sizeof(names[0]), compareNames);
|
|
||||||
|
|
||||||
for (int32_t i = 0; i < (int32_t)arrlen(names); i++) {
|
for (int32_t i = 0; i < (int32_t)arrlen(banks); i++) {
|
||||||
if (bankOfRow(row, names[i]) && strcasecmp(names[i], row->standard) != 0) {
|
if (bankOfRow(row, banks[i]) && strcasecmp(banks[i], row->standard) != 0) {
|
||||||
char label[DVX_MAX_PATH];
|
char label[DVX_MAX_PATH];
|
||||||
|
|
||||||
bankLabel(names[i], row, haveDb ? &db : NULL, label, sizeof(label));
|
bankLabel(banks[i], row, db, label, sizeof(label));
|
||||||
choiceSetLabel(&row->choices, choiceAdd(&row->choices, names[i], CPS_BANK_LABEL), label);
|
choiceSetLabel(&row->choices, choiceAdd(&row->choices, banks[i], CPS_BANK_LABEL), label);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -311,14 +306,7 @@ static int32_t addBanks(BankRowT *row, PrefsHandleT *prefs) {
|
||||||
addFound(row, sMus.romBank, CPS_ROM_BANK);
|
addFound(row, sMus.romBank, CPS_ROM_BANK);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Partly loaded when out of memory: freed whenever it was loaded at all
|
return choiceAdd(&row->choices, prefsGetString(prefs, AUDIO_INI_SECTION, row->key, ""), CPS_BANK_LABEL);
|
||||||
if (sPkg.load) {
|
|
||||||
sPkg.release(&db);
|
|
||||||
}
|
|
||||||
|
|
||||||
dvxReadDirFree(names);
|
|
||||||
sel = choiceAdd(&row->choices, prefsGetString(prefs, AUDIO_INI_SECTION, row->key, ""), CPS_BANK_LABEL);
|
|
||||||
return sel;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -603,8 +591,22 @@ void cpSoundsBuild(AppContextT *ac, WidgetT *page, PrefsHandleT *prefs) {
|
||||||
wgtDropdownSetItems(sModDrop, sModLabels, AUDIO_MODULES_CHOICE_COUNT);
|
wgtDropdownSetItems(sModDrop, sModLabels, AUDIO_MODULES_CHOICE_COUNT);
|
||||||
wgtDropdownSetSelected(sModDrop, sSnd.modules(prefs));
|
wgtDropdownSetSelected(sModDrop, sSnd.modules(prefs));
|
||||||
|
|
||||||
addBankRow(page, "Instruments:", &sFmBanks, prefs);
|
// The banks folders and the package records (they name the add-on
|
||||||
addBankRow(page, "SoundFont:", &sSoundFonts, prefs);
|
// banks), read once for both rows
|
||||||
|
char **banks = platformKindReadDir(DvxKindBanksE);
|
||||||
|
PkgDbT db;
|
||||||
|
bool haveDb = sPkg.load && sPkg.load(&db, "");
|
||||||
|
|
||||||
|
qsort(banks, arrlen(banks), sizeof(banks[0]), compareNames);
|
||||||
|
addBankRow(page, "Instruments:", &sFmBanks, prefs, banks, haveDb ? &db : NULL);
|
||||||
|
addBankRow(page, "SoundFont:", &sSoundFonts, prefs, banks, haveDb ? &db : NULL);
|
||||||
|
|
||||||
|
// Partly loaded when out of memory: freed whenever it was loaded at all
|
||||||
|
if (sPkg.load) {
|
||||||
|
sPkg.release(&db);
|
||||||
|
}
|
||||||
|
|
||||||
|
dvxReadDirFree(banks);
|
||||||
|
|
||||||
AudioInfoT info;
|
AudioInfoT info;
|
||||||
char text[CPS_TEXT_BUF];
|
char text[CPS_TEXT_BUF];
|
||||||
|
|
|
||||||
|
|
@ -1,24 +1,24 @@
|
||||||
// The MIT License (MIT)
|
# The MIT License (MIT)
|
||||||
//
|
#
|
||||||
// Copyright (C) 2026 Scott Duensing
|
# Copyright (C) 2026 Scott Duensing
|
||||||
//
|
#
|
||||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
// of this software and associated documentation files (the "Software"), to
|
# of this software and associated documentation files (the "Software"), to
|
||||||
// deal in the Software without restriction, including without limitation the
|
# deal in the Software without restriction, including without limitation the
|
||||||
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||||
// sell copies of the Software, and to permit persons to whom the Software is
|
# sell copies of the Software, and to permit persons to whom the Software is
|
||||||
// furnished to do so, subject to the following conditions:
|
# furnished to do so, subject to the following conditions:
|
||||||
//
|
#
|
||||||
// The above copyright notice and this permission notice shall be included in
|
# The above copyright notice and this permission notice shall be included in
|
||||||
// all copies or substantial portions of the Software.
|
# all copies or substantial portions of the Software.
|
||||||
//
|
#
|
||||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||||
// IN THE SOFTWARE.
|
# IN THE SOFTWARE.
|
||||||
|
|
||||||
# Control Panel help. Compiled to cpanel.hlp next to cpanel.app; F1 and
|
# Control Panel help. Compiled to cpanel.hlp next to cpanel.app; F1 and
|
||||||
# Help > Contents in the Control Panel open it.
|
# Help > Contents in the Control Panel open it.
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,25 @@
|
||||||
|
# The MIT License (MIT)
|
||||||
|
#
|
||||||
|
# Copyright (C) 2026 Scott Duensing
|
||||||
|
#
|
||||||
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
# of this software and associated documentation files (the "Software"), to
|
||||||
|
# deal in the Software without restriction, including without limitation the
|
||||||
|
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||||
|
# sell copies of the Software, and to permit persons to whom the Software is
|
||||||
|
# furnished to do so, subject to the following conditions:
|
||||||
|
#
|
||||||
|
# The above copyright notice and this permission notice shall be included in
|
||||||
|
# all copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||||
|
# IN THE SOFTWARE.
|
||||||
|
|
||||||
# cpanel.res -- Resource manifest for Control Panel
|
# cpanel.res -- Resource manifest for Control Panel
|
||||||
icon32 icon icon32.bmp
|
icon32 icon icon32.bmp
|
||||||
name text "Control Panel"
|
name text "Control Panel"
|
||||||
|
|
|
||||||
|
|
@ -26,9 +26,10 @@
|
||||||
# basrt.lib -- BASIC runtime library (VM + values + form
|
# basrt.lib -- BASIC runtime library (VM + values + form
|
||||||
# runtime + dlregsym init)
|
# runtime + dlregsym init)
|
||||||
# basdev.lib -- BASIC development library (compiler, build
|
# basdev.lib -- BASIC development library (compiler, build
|
||||||
# pipeline, validator, debugger; the IDE and
|
# pipeline, validator, debugger, the .frm
|
||||||
# BASIC programs through compiler.bas /
|
# model; the IDE and BASIC programs through
|
||||||
# debug.bas; standalone stub embedded)
|
# compiler.bas / debug.bas / designer.bas's
|
||||||
|
# Frm*; standalone stub embedded)
|
||||||
# dvxbasic.app -- BASIC IDE (UI, links against basrt.lib and
|
# dvxbasic.app -- BASIC IDE (UI, links against basrt.lib and
|
||||||
# basdev.lib)
|
# basdev.lib)
|
||||||
# basstub.app -- standalone app stub (obj/dvxbasic, embedded
|
# basstub.app -- standalone app stub (obj/dvxbasic, embedded
|
||||||
|
|
@ -65,23 +66,25 @@ SYSTEMDIR = ../../../../bin/system
|
||||||
COMP_SRCS = compiler/lexer.c compiler/ast.c compiler/scope.c compiler/parse.c compiler/parseExpr.c compiler/parseStmt.c compiler/parseDecl.c compiler/resolve.c compiler/resolveExpr.c compiler/typecheck.c compiler/fold.c compiler/gen.c compiler/genExpr.c compiler/genStmt.c compiler/compiler.c compiler/codegen.c compiler/strip.c compiler/obfuscate.c compiler/compact.c
|
COMP_SRCS = compiler/lexer.c compiler/ast.c compiler/scope.c compiler/parse.c compiler/parseExpr.c compiler/parseStmt.c compiler/parseDecl.c compiler/resolve.c compiler/resolveExpr.c compiler/typecheck.c compiler/fold.c compiler/gen.c compiler/genExpr.c compiler/genStmt.c compiler/compiler.c compiler/codegen.c compiler/strip.c compiler/obfuscate.c compiler/compact.c
|
||||||
VM_SRCS = runtime/vm.c runtime/values.c runtime/dates.c runtime/serialize.c runtime/gfx.c runtime/screen.c
|
VM_SRCS = runtime/vm.c runtime/values.c runtime/dates.c runtime/serialize.c runtime/gfx.c runtime/screen.c
|
||||||
# Standalone programs that link the VM also need what libdvx provides it
|
# Standalone programs that link the VM also need what libdvx provides it
|
||||||
# at run time on the target (INI access through dvxPrefs).
|
# at run time on the target (INI access through dvxPrefs, Unicode through
|
||||||
VM_LINK_SRCS = $(VM_SRCS) $(LIBDVX)/dvxPrefs.c
|
# dvxFontFmt).
|
||||||
|
VM_LINK_SRCS = $(VM_SRCS) $(LIBDVX)/dvxPrefs.c $(LIBDVX)/dvxFontFmt.c
|
||||||
SUPPORT_SRCS = $(LIBDVX)/platform/dvxPlatformUtil.c $(LIBDVX)/thirdparty/stb_ds_impl.c
|
SUPPORT_SRCS = $(LIBDVX)/platform/dvxPlatformUtil.c $(LIBDVX)/thirdparty/stb_ds_impl.c
|
||||||
RES_SRCS = $(LIBDVX)/dvxResource.c $(LIBDVX)/dvxFontFmt.c $(TOOLSDIR)/dvxResWrite.c
|
RES_SRCS = $(LIBDVX)/dvxResource.c $(TOOLSDIR)/dvxResWrite.c
|
||||||
CLI_SRCS = basBuild.c $(COMP_SRCS) $(VM_LINK_SRCS) $(RES_SRCS) $(SUPPORT_SRCS)
|
CLI_SRCS = basBuild.c $(COMP_SRCS) $(VM_LINK_SRCS) $(RES_SRCS) $(SUPPORT_SRCS)
|
||||||
|
|
||||||
# Runtime library objects (VM + values + form runtime + serialization)
|
# Runtime library objects (VM + values + form runtime + serialization)
|
||||||
RT_OBJS = $(OBJDIR)/vm.o $(OBJDIR)/values.o $(OBJDIR)/dates.o $(OBJDIR)/gfx.o $(OBJDIR)/screen.o $(OBJDIR)/formrt.o $(OBJDIR)/formGfx.o $(OBJDIR)/formPrinter.o $(OBJDIR)/formDesign.o $(OBJDIR)/formCtl.o $(OBJDIR)/frmParser.o $(OBJDIR)/frmModel.o $(OBJDIR)/frmModelBas.o $(OBJDIR)/basNativeDos.o $(OBJDIR)/serialize.o $(OBJDIR)/basRunHost.o
|
RT_OBJS = $(OBJDIR)/vm.o $(OBJDIR)/values.o $(OBJDIR)/dates.o $(OBJDIR)/gfx.o $(OBJDIR)/screen.o $(OBJDIR)/formrt.o $(OBJDIR)/formGfx.o $(OBJDIR)/formPrinter.o $(OBJDIR)/formDesign.o $(OBJDIR)/formCtl.o $(OBJDIR)/frmParser.o $(OBJDIR)/basNativeDos.o $(OBJDIR)/serialize.o $(OBJDIR)/basRunHost.o
|
||||||
RT_TARGETDIR = $(LIBSDIR)/kpunch/basrt
|
RT_TARGETDIR = $(LIBSDIR)/kpunch/basrt
|
||||||
RT_TARGET = $(RT_TARGETDIR)/basrt.lib
|
RT_TARGET = $(RT_TARGETDIR)/basrt.lib
|
||||||
|
|
||||||
# Compiler objects (the development library)
|
# Compiler objects (the development library)
|
||||||
COMP_OBJS = $(patsubst %.c,$(OBJDIR)/%.o,$(notdir $(COMP_SRCS))) $(OBJDIR)/basBuild.o
|
COMP_OBJS = $(patsubst %.c,$(OBJDIR)/%.o,$(notdir $(COMP_SRCS))) $(OBJDIR)/basBuild.o
|
||||||
|
|
||||||
# Development library: the compiler plus the shared validator, debugger
|
# Development library: the compiler plus the shared validator, debugger,
|
||||||
# and proc scanner and the BASIC entry points (basdev/)
|
# proc scanner, the .frm model the designer works on, and the BASIC
|
||||||
DEV_SRCS = basdev/basValidate.c basdev/basDebug.c basdev/basProcs.c basdev/basSource.c basdev/basHelpMap.c basdev/basProject.c basdev/basDevBas.c
|
# entry points (basdev/: compiler.bas, debug.bas, designer.bas's Frm*)
|
||||||
|
DEV_SRCS = basdev/basValidate.c basdev/basDebug.c basdev/basProcs.c basdev/basSource.c basdev/basHelpMap.c basdev/basProject.c basdev/basDevBas.c basdev/frmModel.c basdev/frmModelBas.c
|
||||||
DEV_OBJS = $(COMP_OBJS) $(patsubst basdev/%.c,$(OBJDIR)/%.o,$(DEV_SRCS))
|
DEV_OBJS = $(COMP_OBJS) $(patsubst basdev/%.c,$(OBJDIR)/%.o,$(DEV_SRCS))
|
||||||
DEV_TARGETDIR = $(LIBSDIR)/kpunch/basdev
|
DEV_TARGETDIR = $(LIBSDIR)/kpunch/basdev
|
||||||
DEV_TARGET = $(DEV_TARGETDIR)/basdev.lib
|
DEV_TARGET = $(DEV_TARGETDIR)/basdev.lib
|
||||||
|
|
@ -311,7 +314,7 @@ clean: host-basrt-clean
|
||||||
# of the host build.
|
# of the host build.
|
||||||
HOSTRT_DIR = $(HOST_OBJROOT)/basrt
|
HOSTRT_DIR = $(HOST_OBJROOT)/basrt
|
||||||
HOSTRT_TARGET = $(HOST_OBJROOT)/basrt.a
|
HOSTRT_TARGET = $(HOST_OBJROOT)/basrt.a
|
||||||
HOSTRT_SRCS = $(COMP_SRCS) $(VM_SRCS) formrt/formrt.c formrt/formGfx.c formrt/formPrinter.c formrt/formDesign.c formrt/formCtl.c formrt/frmParser.c formrt/frmModel.c formrt/frmModelBas.c formrt/basNativeHost.c formrt/basRunHost.c
|
HOSTRT_SRCS = $(COMP_SRCS) $(VM_SRCS) formrt/formrt.c formrt/formGfx.c formrt/formPrinter.c formrt/formDesign.c formrt/formCtl.c formrt/frmParser.c basdev/frmModel.c basdev/frmModelBas.c formrt/basNativeHost.c formrt/basRunHost.c
|
||||||
HOSTRT_OBJS = $(patsubst %.c,$(HOSTRT_DIR)/%.o,$(notdir $(HOSTRT_SRCS)))
|
HOSTRT_OBJS = $(patsubst %.c,$(HOSTRT_DIR)/%.o,$(notdir $(HOSTRT_SRCS)))
|
||||||
HOSTRT_CFLAGS = $(HOST_CFLAGS) -Wno-stringop-truncation $(DVX_INCLUDES) -I../../../widgets/kpunch -I../../../libs/kpunch/dvxshell -I../../../libs/kpunch/libtasks
|
HOSTRT_CFLAGS = $(HOST_CFLAGS) -Wno-stringop-truncation $(DVX_INCLUDES) -I../../../widgets/kpunch -I../../../libs/kpunch/dvxshell -I../../../libs/kpunch/libtasks
|
||||||
|
|
||||||
|
|
@ -335,6 +338,10 @@ $(HOSTRT_DIR)/%.o: formrt/%.c $(DVX_MAKEFILES)
|
||||||
@mkdir -p $(HOSTRT_DIR)
|
@mkdir -p $(HOSTRT_DIR)
|
||||||
$(HOSTCC) $(HOSTRT_CFLAGS) -c -o $@ $<
|
$(HOSTCC) $(HOSTRT_CFLAGS) -c -o $@ $<
|
||||||
|
|
||||||
|
$(HOSTRT_DIR)/%.o: basdev/%.c $(DVX_MAKEFILES)
|
||||||
|
@mkdir -p $(HOSTRT_DIR)
|
||||||
|
$(HOSTCC) $(HOSTRT_CFLAGS) -c -o $@ $<
|
||||||
|
|
||||||
host-basrt-clean:
|
host-basrt-clean:
|
||||||
rm -rf $(HOSTRT_DIR) $(HOSTRT_TARGET)
|
rm -rf $(HOSTRT_DIR) $(HOSTRT_TARGET)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,8 @@
|
||||||
// .frm texts too), compact the bytecode and re-serialize.
|
// .frm texts too), compact the bytecode and re-serialize.
|
||||||
// 3. Debug builds serialize the debug info.
|
// 3. Debug builds serialize the debug info.
|
||||||
// 4. Extract the STUB resource from selfPath and write it as outPath.
|
// 4. Extract the STUB resource from selfPath and write it as outPath.
|
||||||
// 5. Append the resource set in canonical order:
|
// 5. Gather the resource set in memory (a DvxResBatchT, written to
|
||||||
|
// outPath once at the end) in canonical order:
|
||||||
// BAS_RES_NAME (always, falls back to BAS_DEFAULT_APP_NAME)
|
// BAS_RES_NAME (always, falls back to BAS_DEFAULT_APP_NAME)
|
||||||
// BAS_RES_AUTHOR / PUBLISHER / VERSION / COPYRIGHT / DESCRIPTION
|
// BAS_RES_AUTHOR / PUBLISHER / VERSION / COPYRIGHT / DESCRIPTION
|
||||||
// BAS_RES_ICON32 (project icon, else the NOICON fallback)
|
// BAS_RES_ICON32 (project icon, else the NOICON fallback)
|
||||||
|
|
@ -47,6 +48,7 @@
|
||||||
// 6. Copy the help file next to outPath.
|
// 6. Copy the help file next to outPath.
|
||||||
|
|
||||||
#include "basBuild.h"
|
#include "basBuild.h"
|
||||||
|
#include "../../../tools/dvxResWrite.h"
|
||||||
#include "basRes.h"
|
#include "basRes.h"
|
||||||
#include "compiler/compiler.h"
|
#include "compiler/compiler.h"
|
||||||
#include "compiler/compact.h"
|
#include "compiler/compact.h"
|
||||||
|
|
@ -121,11 +123,11 @@ static const char *const sWrapEnd[] = {
|
||||||
|
|
||||||
// Function prototypes (alphabetical)
|
// Function prototypes (alphabetical)
|
||||||
static bool appendSource(BasProjectSourceT *ps, const char *text, int32_t len);
|
static bool appendSource(BasProjectSourceT *ps, const char *text, int32_t len);
|
||||||
static int32_t appendText(const char *path, const char *name, const char *value);
|
static int32_t appendText(DvxResBatchT *res, const char *name, const char *value);
|
||||||
bool basBuildAddSource(BasProjectSourceT *ps, const char *path, const char *text, int32_t len, BasBuildWrapE wrap);
|
bool basBuildAddSource(BasProjectSourceT *ps, const char *path, const char *text, int32_t len, BasBuildWrapE wrap);
|
||||||
const char *basBuildApp(const char *outPath, const BasBuildSpecT *spec);
|
const char *basBuildApp(const char *outPath, const BasBuildSpecT *spec);
|
||||||
bool basBuildAppendFile(BasProjectSourceT *ps, const char *text, int32_t len, BasBuildWrapE wrap, const char *name, const char *ctrlList, int32_t *textLine, int32_t *textLines);
|
bool basBuildAppendFile(BasProjectSourceT *ps, const char *text, int32_t len, BasBuildWrapE wrap, const char *name, const char *ctrlList, int32_t *textLine, int32_t *textLines);
|
||||||
BasModuleT *basBuildCompile(const char *source, int32_t len, bool optionExplicit, const BasCtrlValidatorT *validator, const BasCompilerT **liveCompiler, BasCompileErrorT *err);
|
BasModuleT *basBuildCompile(const char *source, int32_t len, bool optionExplicit, const BasCtrlValidatorT *validator, const BasCompilerT **liveCompiler, BasCompileErrorT *err, void (*unitFn)(void *ctx), void *unitCtx);
|
||||||
bool basBuildConcatProject(PrefsHandleT *prefs, const char *projectDir, BasProjectSourceT *out);
|
bool basBuildConcatProject(PrefsHandleT *prefs, const char *projectDir, BasProjectSourceT *out);
|
||||||
const char *basBuildControl(const char *outPath, const BasBuildSpecT *spec);
|
const char *basBuildControl(const char *outPath, const BasBuildSpecT *spec);
|
||||||
const char *basBuildErrorText(const char *message);
|
const char *basBuildErrorText(const char *message);
|
||||||
|
|
@ -140,11 +142,11 @@ BasBuildWrapE basBuildWrapForPath(const char *path);
|
||||||
static void buildLog(const BasBuildSpecT *spec, const char *fmt, ...);
|
static void buildLog(const BasBuildSpecT *spec, const char *fmt, ...);
|
||||||
static const char *copyHelpFile(const char *outPath, const BasBuildSpecT *spec);
|
static const char *copyHelpFile(const char *outPath, const BasBuildSpecT *spec);
|
||||||
static int32_t countLines(const char *text, int32_t len);
|
static int32_t countLines(const char *text, int32_t len);
|
||||||
static const char *emitControls(const char *outPath, const BasBuildSpecT *spec);
|
static const char *emitControls(DvxResBatchT *res, const BasBuildSpecT *spec);
|
||||||
static const char *emitCtlTexts(const char *outPath, const BasBuildSpecT *spec);
|
static const char *emitCtlTexts(DvxResBatchT *res, const BasBuildSpecT *spec);
|
||||||
static const char *emitFonts(const char *outPath, const BasBuildSpecT *spec);
|
static const char *emitFonts(DvxResBatchT *res, const BasBuildSpecT *spec);
|
||||||
static const char *emitIcon(const char *outPath, const BasBuildSpecT *spec);
|
static const char *emitIcon(DvxResBatchT *res, const BasBuildSpecT *spec);
|
||||||
static const char *emitResources(const char *outPath, const BasBuildSpecT *spec, const uint8_t *modData, int32_t modLen, const uint8_t *dbgData, int32_t dbgLen, uint8_t **frmData, const int32_t *frmLens, const BasObfFrmT *obfFrms, int32_t frmCount);
|
static const char *emitResources(DvxResBatchT *res, const BasBuildSpecT *spec, const uint8_t *modData, int32_t modLen, const uint8_t *dbgData, int32_t dbgLen, uint8_t **frmData, const int32_t *frmLens, const BasObfFrmT *obfFrms, int32_t frmCount);
|
||||||
static void freeFrmWork(uint8_t **frmData, int32_t *frmLens, BasObfFrmT *obfFrms);
|
static void freeFrmWork(uint8_t **frmData, int32_t *frmLens, BasObfFrmT *obfFrms);
|
||||||
static const char *prepareModule(const BasBuildSpecT *spec, uint8_t **frmData, int32_t *frmLens, BasObfFrmT *obfFrms, int32_t frmCount, uint8_t **outMod, int32_t *outModLen, uint8_t **outDbg, int32_t *outDbgLen);
|
static const char *prepareModule(const BasBuildSpecT *spec, uint8_t **frmData, int32_t *frmLens, BasObfFrmT *obfFrms, int32_t frmCount, uint8_t **outMod, int32_t *outModLen, uint8_t **outDbg, int32_t *outDbgLen);
|
||||||
static const char *stubCarrier(const BasBuildSpecT *spec);
|
static const char *stubCarrier(const BasBuildSpecT *spec);
|
||||||
|
|
@ -194,12 +196,12 @@ static bool appendSource(BasProjectSourceT *ps, const char *text, int32_t len) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static int32_t appendText(const char *path, const char *name, const char *value) {
|
static int32_t appendText(DvxResBatchT *res, const char *name, const char *value) {
|
||||||
if (!value || !value[0]) {
|
if (!value || !value[0]) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
return dvxResAppend(path, name, DVX_RES_TEXT, value, (uint32_t)strlen(value) + 1);
|
return dvxResBatchAdd(res, name, DVX_RES_TEXT, value, (uint32_t)strlen(value) + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -349,16 +351,31 @@ const char *basBuildApp(const char *outPath, const BasBuildSpecT *spec) {
|
||||||
int32_t dbgLen = 0;
|
int32_t dbgLen = 0;
|
||||||
const char *failure = prepareModule(spec, frmData, frmLens, obfFrms, frmCount, &modData, &modLen, &dbgData, &dbgLen);
|
const char *failure = prepareModule(spec, frmData, frmLens, obfFrms, frmCount, &modData, &modLen, &dbgData, &dbgLen);
|
||||||
|
|
||||||
|
// Every resource is gathered and the file written once
|
||||||
|
DvxResBatchT res;
|
||||||
|
bool batched = false;
|
||||||
|
|
||||||
if (!failure) {
|
if (!failure) {
|
||||||
failure = writeStub(outPath, spec);
|
failure = writeStub(outPath, spec);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!failure) {
|
if (!failure) {
|
||||||
failure = emitResources(outPath, spec, modData, modLen, dbgData, dbgLen, frmData, frmLens, obfFrms, frmCount);
|
batched = dvxResBatchBegin(&res, outPath) == 0;
|
||||||
|
failure = batched ? NULL : BAS_BUILD_ERR_WRITE_RES;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!failure) {
|
if (!failure) {
|
||||||
failure = emitFonts(outPath, spec);
|
failure = emitResources(&res, spec, modData, modLen, dbgData, dbgLen, frmData, frmLens, obfFrms, frmCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!failure) {
|
||||||
|
failure = emitFonts(&res, spec);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (batched && failure) {
|
||||||
|
dvxResBatchFree(&res);
|
||||||
|
} else if (batched && dvxResBatchCommit(&res) != 0) {
|
||||||
|
failure = BAS_BUILD_ERR_WRITE_RES;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!failure) {
|
if (!failure) {
|
||||||
|
|
@ -418,7 +435,7 @@ bool basBuildAppendFile(BasProjectSourceT *ps, const char *text, int32_t len, Ba
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
BasModuleT *basBuildCompile(const char *source, int32_t len, bool optionExplicit, const BasCtrlValidatorT *validator, const BasCompilerT **liveCompiler, BasCompileErrorT *err) {
|
BasModuleT *basBuildCompile(const char *source, int32_t len, bool optionExplicit, const BasCtrlValidatorT *validator, const BasCompilerT **liveCompiler, BasCompileErrorT *err, void (*unitFn)(void *ctx), void *unitCtx) {
|
||||||
memset(err, 0, sizeof(*err));
|
memset(err, 0, sizeof(*err));
|
||||||
|
|
||||||
BasCompilerT *compiler = basCompilerCreate(source, len);
|
BasCompilerT *compiler = basCompilerCreate(source, len);
|
||||||
|
|
@ -430,6 +447,7 @@ BasModuleT *basBuildCompile(const char *source, int32_t len, bool optionExplicit
|
||||||
}
|
}
|
||||||
|
|
||||||
basCompilerSetOptionExplicit(compiler, optionExplicit);
|
basCompilerSetOptionExplicit(compiler, optionExplicit);
|
||||||
|
basCompilerSetUnitFn(compiler, unitFn, unitCtx);
|
||||||
|
|
||||||
if (validator) {
|
if (validator) {
|
||||||
basCompilerSetValidator(compiler, validator);
|
basCompilerSetValidator(compiler, validator);
|
||||||
|
|
@ -539,16 +557,31 @@ const char *basBuildControl(const char *outPath, const BasBuildSpecT *spec) {
|
||||||
int32_t dbgLen = 0;
|
int32_t dbgLen = 0;
|
||||||
const char *failure = prepareModule(spec, NULL, NULL, NULL, 0, &modData, &modLen, &dbgData, &dbgLen);
|
const char *failure = prepareModule(spec, NULL, NULL, NULL, 0, &modData, &modLen, &dbgData, &dbgLen);
|
||||||
|
|
||||||
|
// Every resource is gathered and the file written once
|
||||||
|
DvxResBatchT res;
|
||||||
|
bool batched = false;
|
||||||
|
|
||||||
if (!failure) {
|
if (!failure) {
|
||||||
failure = writeMarker(outPath);
|
failure = writeMarker(outPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!failure && (dvxResAppend(outPath, BAS_RES_MODULE, DVX_RES_BINARY, modData, (uint32_t)modLen) != 0 || (dbgData && dvxResAppend(outPath, BAS_RES_DEBUG, DVX_RES_BINARY, dbgData, (uint32_t)dbgLen) != 0))) {
|
if (!failure) {
|
||||||
|
batched = dvxResBatchBegin(&res, outPath) == 0;
|
||||||
|
failure = batched ? NULL : BAS_BUILD_ERR_WRITE_RES;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!failure && (dvxResBatchAdd(&res, BAS_RES_MODULE, DVX_RES_BINARY, modData, (uint32_t)modLen) != 0 || (dbgData && dvxResBatchAdd(&res, BAS_RES_DEBUG, DVX_RES_BINARY, dbgData, (uint32_t)dbgLen) != 0))) {
|
||||||
failure = BAS_BUILD_ERR_WRITE_RES;
|
failure = BAS_BUILD_ERR_WRITE_RES;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!failure) {
|
if (!failure) {
|
||||||
failure = emitControls(outPath, spec);
|
failure = emitControls(&res, spec);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (batched && failure) {
|
||||||
|
dvxResBatchFree(&res);
|
||||||
|
} else if (batched && dvxResBatchCommit(&res) != 0) {
|
||||||
|
failure = BAS_BUILD_ERR_WRITE_RES;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!failure) {
|
if (!failure) {
|
||||||
|
|
@ -765,17 +798,17 @@ static int32_t countLines(const char *text, int32_t len) {
|
||||||
// picture (icon24, the header's ToolboxIcon, a file in the project
|
// picture (icon24, the header's ToolboxIcon, a file in the project
|
||||||
// folder) and its toolbox name (the header's Description, else its own
|
// folder) and its toolbox name (the header's Description, else its own
|
||||||
// name), the second and later with "-n" suffixes.
|
// name), the second and later with "-n" suffixes.
|
||||||
static const char *emitControls(const char *outPath, const BasBuildSpecT *spec) {
|
static const char *emitControls(DvxResBatchT *res, const BasBuildSpecT *spec) {
|
||||||
int32_t rc = 0;
|
int32_t rc = 0;
|
||||||
|
|
||||||
rc |= appendText(outPath, BAS_RES_AUTHOR, spec->author);
|
rc |= appendText(res, BAS_RES_AUTHOR, spec->author);
|
||||||
rc |= appendText(outPath, BAS_RES_PUBLISHER, spec->publisher);
|
rc |= appendText(res, BAS_RES_PUBLISHER, spec->publisher);
|
||||||
rc |= appendText(outPath, BAS_RES_VERSION, spec->version);
|
rc |= appendText(res, BAS_RES_VERSION, spec->version);
|
||||||
rc |= appendText(outPath, BAS_RES_COPYRIGHT, spec->copyright);
|
rc |= appendText(res, BAS_RES_COPYRIGHT, spec->copyright);
|
||||||
rc |= appendText(outPath, BAS_RES_DESCRIPTION, spec->description);
|
rc |= appendText(res, BAS_RES_DESCRIPTION, spec->description);
|
||||||
|
|
||||||
if (spec->helpFile && spec->helpFile[0]) {
|
if (spec->helpFile && spec->helpFile[0]) {
|
||||||
rc |= appendText(outPath, BAS_RES_HELPFILE, platformPathBaseName(spec->helpFile));
|
rc |= appendText(res, BAS_RES_HELPFILE, platformPathBaseName(spec->helpFile));
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int32_t i = 0; rc == 0 && i < spec->ctlCount; i++) {
|
for (int32_t i = 0; rc == 0 && i < spec->ctlCount; i++) {
|
||||||
|
|
@ -799,7 +832,7 @@ static const char *emitControls(const char *outPath, const BasBuildSpecT *spec)
|
||||||
}
|
}
|
||||||
|
|
||||||
snprintf(resName, sizeof(resName), (index == BAS_BUILD_CTL_FIRST) ? "%s" : BAS_CTL_RES_SUFFIX_FMT, BAS_CTL_RES_ICON, (int)index);
|
snprintf(resName, sizeof(resName), (index == BAS_BUILD_CTL_FIRST) ? "%s" : BAS_CTL_RES_SUFFIX_FMT, BAS_CTL_RES_ICON, (int)index);
|
||||||
rc |= dvxResAppend(outPath, resName, DVX_RES_ICON, iconData, (uint32_t)iconLen);
|
rc |= dvxResBatchAdd(res, resName, DVX_RES_ICON, iconData, (uint32_t)iconLen);
|
||||||
free(iconData);
|
free(iconData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -808,19 +841,19 @@ static const char *emitControls(const char *outPath, const BasBuildSpecT *spec)
|
||||||
}
|
}
|
||||||
|
|
||||||
snprintf(resName, sizeof(resName), (index == BAS_BUILD_CTL_FIRST) ? "%s" : BAS_CTL_RES_SUFFIX_FMT, BAS_CTL_RES_NAME, (int)index);
|
snprintf(resName, sizeof(resName), (index == BAS_BUILD_CTL_FIRST) ? "%s" : BAS_CTL_RES_SUFFIX_FMT, BAS_CTL_RES_NAME, (int)index);
|
||||||
rc |= appendText(outPath, resName, value);
|
rc |= appendText(res, resName, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rc != 0) {
|
if (rc != 0) {
|
||||||
return BAS_BUILD_ERR_WRITE_RES;
|
return BAS_BUILD_ERR_WRITE_RES;
|
||||||
}
|
}
|
||||||
|
|
||||||
return emitCtlTexts(outPath, spec);
|
return emitCtlTexts(res, spec);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// The UserControls' header texts, CTL0, CTL1 ...
|
// The UserControls' header texts, CTL0, CTL1 ...
|
||||||
static const char *emitCtlTexts(const char *outPath, const BasBuildSpecT *spec) {
|
static const char *emitCtlTexts(DvxResBatchT *res, const BasBuildSpecT *spec) {
|
||||||
if (spec->ctlCount > BAS_MAX_CTL_RESOURCES) {
|
if (spec->ctlCount > BAS_MAX_CTL_RESOURCES) {
|
||||||
return "Too many UserControls in one project.";
|
return "Too many UserControls in one project.";
|
||||||
}
|
}
|
||||||
|
|
@ -830,7 +863,7 @@ static const char *emitCtlTexts(const char *outPath, const BasBuildSpecT *spec)
|
||||||
|
|
||||||
snprintf(resName, sizeof(resName), BAS_RES_CTL_FMT, (long)i);
|
snprintf(resName, sizeof(resName), BAS_RES_CTL_FMT, (long)i);
|
||||||
|
|
||||||
if (appendText(outPath, resName, spec->ctlSources[i]) != 0) {
|
if (appendText(res, resName, spec->ctlSources[i]) != 0) {
|
||||||
return BAS_BUILD_ERR_WRITE_RES;
|
return BAS_BUILD_ERR_WRITE_RES;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -841,7 +874,7 @@ static const char *emitCtlTexts(const char *outPath, const BasBuildSpecT *spec)
|
||||||
|
|
||||||
// The fonts the program carries as FONTn resources, each checked to be a
|
// The fonts the program carries as FONTn resources, each checked to be a
|
||||||
// DVX font first
|
// DVX font first
|
||||||
static const char *emitFonts(const char *outPath, const BasBuildSpecT *spec) {
|
static const char *emitFonts(DvxResBatchT *res, const BasBuildSpecT *spec) {
|
||||||
if (spec->fontCount > BAS_BUILD_FONTS_MAX) {
|
if (spec->fontCount > BAS_BUILD_FONTS_MAX) {
|
||||||
snprintf(sFontError, sizeof(sFontError), "A program carries at most %d fonts.", BAS_BUILD_FONTS_MAX);
|
snprintf(sFontError, sizeof(sFontError), "A program carries at most %d fonts.", BAS_BUILD_FONTS_MAX);
|
||||||
return sFontError;
|
return sFontError;
|
||||||
|
|
@ -877,7 +910,7 @@ static const char *emitFonts(const char *outPath, const BasBuildSpecT *spec) {
|
||||||
|
|
||||||
snprintf(name, sizeof(name), BAS_RES_FONT_FMT, (long)i);
|
snprintf(name, sizeof(name), BAS_RES_FONT_FMT, (long)i);
|
||||||
|
|
||||||
int32_t rc = dvxResAppend(outPath, name, DVX_RES_FONT, data, (uint32_t)len);
|
int32_t rc = dvxResBatchAdd(res, name, DVX_RES_FONT, data, (uint32_t)len);
|
||||||
|
|
||||||
free(data);
|
free(data);
|
||||||
|
|
||||||
|
|
@ -895,7 +928,7 @@ static const char *emitFonts(const char *outPath, const BasBuildSpecT *spec) {
|
||||||
// Embed the project icon, or the NOICON fallback of the stub carrier
|
// Embed the project icon, or the NOICON fallback of the stub carrier
|
||||||
// when the project names none. Returns NULL on success, else
|
// when the project names none. Returns NULL on success, else
|
||||||
// an error message (a named icon that cannot be read names the path).
|
// an error message (a named icon that cannot be read names the path).
|
||||||
static const char *emitIcon(const char *outPath, const BasBuildSpecT *spec) {
|
static const char *emitIcon(DvxResBatchT *res, const BasBuildSpecT *spec) {
|
||||||
char iconPath[DVX_MAX_PATH];
|
char iconPath[DVX_MAX_PATH];
|
||||||
void *iconData = NULL;
|
void *iconData = NULL;
|
||||||
int32_t iconLen = 0;
|
int32_t iconLen = 0;
|
||||||
|
|
@ -927,13 +960,13 @@ static const char *emitIcon(const char *outPath, const BasBuildSpecT *spec) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int32_t rc = dvxResAppend(outPath, BAS_RES_ICON32, DVX_RES_ICON, iconData, (uint32_t)iconLen);
|
int32_t rc = dvxResBatchAdd(res, BAS_RES_ICON32, DVX_RES_ICON, iconData, (uint32_t)iconLen);
|
||||||
free(iconData);
|
free(iconData);
|
||||||
return (rc == 0) ? NULL : BAS_BUILD_ERR_WRITE_RES;
|
return (rc == 0) ? NULL : BAS_BUILD_ERR_WRITE_RES;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static const char *emitResources(const char *outPath, const BasBuildSpecT *spec, const uint8_t *modData, int32_t modLen, const uint8_t *dbgData, int32_t dbgLen, uint8_t **frmData, const int32_t *frmLens, const BasObfFrmT *obfFrms, int32_t frmCount) {
|
static const char *emitResources(DvxResBatchT *res, const BasBuildSpecT *spec, const uint8_t *modData, int32_t modLen, const uint8_t *dbgData, int32_t dbgLen, uint8_t **frmData, const int32_t *frmLens, const BasObfFrmT *obfFrms, int32_t frmCount) {
|
||||||
// Accumulate every append result so a disk-full or I/O failure on any
|
// Accumulate every append result so a disk-full or I/O failure on any
|
||||||
// resource is reported instead of being silently treated as success.
|
// resource is reported instead of being silently treated as success.
|
||||||
int32_t rc = 0;
|
int32_t rc = 0;
|
||||||
|
|
@ -941,32 +974,32 @@ static const char *emitResources(const char *outPath, const BasBuildSpecT *spec,
|
||||||
// Project metadata. Name is required -- fall back to a generic label
|
// Project metadata. Name is required -- fall back to a generic label
|
||||||
// so the compiled app always has something sensible to display.
|
// so the compiled app always has something sensible to display.
|
||||||
const char *projName = (spec->projName && spec->projName[0]) ? spec->projName : BAS_DEFAULT_APP_NAME;
|
const char *projName = (spec->projName && spec->projName[0]) ? spec->projName : BAS_DEFAULT_APP_NAME;
|
||||||
rc |= appendText(outPath, BAS_RES_NAME, projName);
|
rc |= appendText(res, BAS_RES_NAME, projName);
|
||||||
|
|
||||||
rc |= appendText(outPath, BAS_RES_AUTHOR, spec->author);
|
rc |= appendText(res, BAS_RES_AUTHOR, spec->author);
|
||||||
rc |= appendText(outPath, BAS_RES_PUBLISHER, spec->publisher);
|
rc |= appendText(res, BAS_RES_PUBLISHER, spec->publisher);
|
||||||
rc |= appendText(outPath, BAS_RES_VERSION, spec->version);
|
rc |= appendText(res, BAS_RES_VERSION, spec->version);
|
||||||
rc |= appendText(outPath, BAS_RES_COPYRIGHT, spec->copyright);
|
rc |= appendText(res, BAS_RES_COPYRIGHT, spec->copyright);
|
||||||
rc |= appendText(outPath, BAS_RES_DESCRIPTION, spec->description);
|
rc |= appendText(res, BAS_RES_DESCRIPTION, spec->description);
|
||||||
|
|
||||||
const char *iconFailure = emitIcon(outPath, spec);
|
const char *iconFailure = emitIcon(res, spec);
|
||||||
|
|
||||||
if (iconFailure) {
|
if (iconFailure) {
|
||||||
return iconFailure;
|
return iconFailure;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (spec->helpFile && spec->helpFile[0]) {
|
if (spec->helpFile && spec->helpFile[0]) {
|
||||||
rc |= appendText(outPath, BAS_RES_HELPFILE, platformPathBaseName(spec->helpFile));
|
rc |= appendText(res, BAS_RES_HELPFILE, platformPathBaseName(spec->helpFile));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (spec->multiInstance) {
|
if (spec->multiInstance) {
|
||||||
rc |= appendText(outPath, BAS_RES_MULTIINSTANCE, BAS_RES_FLAG_ON);
|
rc |= appendText(res, BAS_RES_MULTIINSTANCE, BAS_RES_FLAG_ON);
|
||||||
}
|
}
|
||||||
|
|
||||||
rc |= dvxResAppend(outPath, BAS_RES_MODULE, DVX_RES_BINARY, modData, (uint32_t)modLen);
|
rc |= dvxResBatchAdd(res, BAS_RES_MODULE, DVX_RES_BINARY, modData, (uint32_t)modLen);
|
||||||
|
|
||||||
if (dbgData && dbgLen > 0) {
|
if (dbgData && dbgLen > 0) {
|
||||||
rc |= dvxResAppend(outPath, BAS_RES_DEBUG, DVX_RES_BINARY, dbgData, (uint32_t)dbgLen);
|
rc |= dvxResBatchAdd(res, BAS_RES_DEBUG, DVX_RES_BINARY, dbgData, (uint32_t)dbgLen);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Form resources. Release builds embed the obfuscated variant when
|
// Form resources. Release builds embed the obfuscated variant when
|
||||||
|
|
@ -1002,7 +1035,7 @@ static const char *emitResources(const char *outPath, const BasBuildSpecT *spec,
|
||||||
int32_t formLen = basFindFormEndPos((const char *)data, len);
|
int32_t formLen = basFindFormEndPos((const char *)data, len);
|
||||||
|
|
||||||
snprintf(resName, sizeof(resName), BAS_RES_FORM_FMT, (long)outIdx);
|
snprintf(resName, sizeof(resName), BAS_RES_FORM_FMT, (long)outIdx);
|
||||||
rc |= dvxResAppend(outPath, resName, DVX_RES_BINARY, data, (uint32_t)formLen);
|
rc |= dvxResBatchAdd(res, resName, DVX_RES_BINARY, data, (uint32_t)formLen);
|
||||||
outIdx++;
|
outIdx++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1010,7 +1043,7 @@ static const char *emitResources(const char *outPath, const BasBuildSpecT *spec,
|
||||||
return BAS_BUILD_ERR_WRITE_RES;
|
return BAS_BUILD_ERR_WRITE_RES;
|
||||||
}
|
}
|
||||||
|
|
||||||
return emitCtlTexts(outPath, spec);
|
return emitCtlTexts(res, spec);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -191,8 +191,10 @@ typedef struct {
|
||||||
// optionExplicit is set and validator (may be NULL) checking
|
// optionExplicit is set and validator (may be NULL) checking
|
||||||
// CtrlName.Member references. *liveCompiler (may be NULL) holds the
|
// CtrlName.Member references. *liveCompiler (may be NULL) holds the
|
||||||
// compiler while it runs: a validator reads its form scope from it.
|
// compiler while it runs: a validator reads its form scope from it.
|
||||||
// NULL on failure, with *err telling why.
|
// unitFn(unitCtx) (NULL: none) runs after each unit compiled
|
||||||
BasModuleT *basBuildCompile(const char *source, int32_t len, bool optionExplicit, const BasCtrlValidatorT *validator, const BasCompilerT **liveCompiler, BasCompileErrorT *err);
|
// (basCompilerSetUnitFn), for a caller that lets other work go on. NULL
|
||||||
|
// on failure, with *err telling why.
|
||||||
|
BasModuleT *basBuildCompile(const char *source, int32_t len, bool optionExplicit, const BasCtrlValidatorT *validator, const BasCompilerT **liveCompiler, BasCompileErrorT *err, void (*unitFn)(void *ctx), void *unitCtx);
|
||||||
|
|
||||||
// A compiler message without its "Line N, Col M: " prefix.
|
// A compiler message without its "Line N, Col M: " prefix.
|
||||||
const char *basBuildErrorText(const char *message);
|
const char *basBuildErrorText(const char *message);
|
||||||
|
|
|
||||||
|
|
@ -63,12 +63,12 @@ typedef struct {
|
||||||
} DbgCaptureT;
|
} DbgCaptureT;
|
||||||
|
|
||||||
// Prototypes (alphabetical)
|
// Prototypes (alphabetical)
|
||||||
static bool assignSlot(BasVmT *vm, BasValueT *slot, const char *lhs, int32_t lhsLen, const char *rhs, BasPrintFnT printFn, void *ctx);
|
static bool assignSlot(BasVmT *vm, BasValueT *slot, const char *lhs, int32_t lhsLen, const char *rhs, BasPrintFnT printFn, void *ctx, const BasDbgPumpT *pump);
|
||||||
int32_t basDbgAppendDropping(char *buf, int32_t cap, int32_t len, const char *text, int32_t textLen);
|
int32_t basDbgAppendDropping(char *buf, int32_t cap, int32_t len, const char *text, int32_t textLen);
|
||||||
int32_t basDbgCallStack(BasVmT *vm, int32_t curLine, BasDbgRowFnT fn, void *ctx);
|
int32_t basDbgCallStack(BasVmT *vm, int32_t curLine, BasDbgRowFnT fn, void *ctx);
|
||||||
int32_t basDbgCurProcIdx(const BasVmT *vm);
|
int32_t basDbgCurProcIdx(const BasVmT *vm);
|
||||||
bool basDbgEval(BasVmT *pausedVm, const char *expr, char *out, int32_t outSize);
|
bool basDbgEval(BasVmT *pausedVm, const char *expr, char *out, int32_t outSize, const BasDbgPumpT *pump);
|
||||||
bool basDbgExec(BasVmT *pausedVm, const char *line, BasPrintFnT printFn, void *ctx);
|
bool basDbgExec(BasVmT *pausedVm, const char *line, BasPrintFnT printFn, void *ctx, const BasDbgPumpT *pump);
|
||||||
const BasDebugVarT *basDbgFindVar(const BasVmT *vm, const char *name);
|
const BasDebugVarT *basDbgFindVar(const BasVmT *vm, const char *name);
|
||||||
void basDbgFormatValue(const BasVmT *vm, const BasValueT *v, char *buf, int32_t bufSize);
|
void basDbgFormatValue(const BasVmT *vm, const BasValueT *v, char *buf, int32_t bufSize);
|
||||||
const char *basDbgInstanceTypeName(const BasVmT *vm, const BasObjectT *obj);
|
const char *basDbgInstanceTypeName(const BasVmT *vm, const BasObjectT *obj);
|
||||||
|
|
@ -76,14 +76,14 @@ bool basDbgIsMangledName(const char *name);
|
||||||
int32_t basDbgLocals(BasVmT *vm, BasDbgRowFnT fn, void *ctx);
|
int32_t basDbgLocals(BasVmT *vm, BasDbgRowFnT fn, void *ctx);
|
||||||
const char *basDbgProcNameForPc(const BasVmT *vm, int32_t pc);
|
const char *basDbgProcNameForPc(const BasVmT *vm, int32_t pc);
|
||||||
BasValueT *basDbgResolvePath(BasVmT *vm, const char *path, const char **endPtr);
|
BasValueT *basDbgResolvePath(BasVmT *vm, const char *path, const char **endPtr);
|
||||||
BasVmResultE basDbgRunSnippet(BasVmT *vm);
|
BasVmResultE basDbgRunSnippet(BasVmT *vm, const BasDbgPumpT *pump);
|
||||||
bool basDbgSetVar(BasVmT *pausedVm, const char *path, const char *expr, BasPrintFnT printFn, void *ctx);
|
bool basDbgSetVar(BasVmT *pausedVm, const char *path, const char *expr, BasPrintFnT printFn, void *ctx, const BasDbgPumpT *pump);
|
||||||
BasVmT *basDbgSnippetVm(BasVmT *pausedVm, const char *src, BasModuleT **outMod, char *err, int32_t errSize, const char *captureName, int32_t *outCaptureIdx);
|
BasVmT *basDbgSnippetVm(BasVmT *pausedVm, const char *src, BasModuleT **outMod, char *err, int32_t errSize, const char *captureName, int32_t *outCaptureIdx);
|
||||||
const char *basDbgTypeName(uint8_t dt);
|
const char *basDbgTypeName(uint8_t dt);
|
||||||
BasValueT *basDbgVarSlot(BasVmT *vm, const BasDebugVarT *dv);
|
BasValueT *basDbgVarSlot(BasVmT *vm, const BasDebugVarT *dv);
|
||||||
bool basDbgVarVisible(const BasVmT *vm, const BasDebugVarT *dv, int32_t curProcIdx);
|
bool basDbgVarVisible(const BasVmT *vm, const BasDebugVarT *dv, int32_t curProcIdx);
|
||||||
static void capturePrint(void *ctx, const char *text, bool newline);
|
static void capturePrint(void *ctx, const char *text, bool newline);
|
||||||
static bool evalRhs(BasVmT *vm, const char *rhs, uint8_t targetType, BasValueT *outVal, BasPrintFnT printFn, void *ctx);
|
static bool evalRhs(BasVmT *vm, const char *rhs, uint8_t targetType, BasValueT *outVal, BasPrintFnT printFn, void *ctx, const BasDbgPumpT *pump);
|
||||||
static int32_t findMemberVarIdx(const BasVmT *vm, const BasObjectT *obj, const char *name);
|
static int32_t findMemberVarIdx(const BasVmT *vm, const BasObjectT *obj, const char *name);
|
||||||
static int32_t findUdtFieldIdx(const BasModuleT *mod, int32_t typeId, const char *fieldName);
|
static int32_t findUdtFieldIdx(const BasModuleT *mod, int32_t typeId, const char *fieldName);
|
||||||
static bool isPaused(const BasVmT *vm);
|
static bool isPaused(const BasVmT *vm);
|
||||||
|
|
@ -94,12 +94,12 @@ static const char *parseIndexList(const char *p, int32_t *indices, int32_t *out
|
||||||
static void printError(BasPrintFnT printFn, void *ctx, const char *msg);
|
static void printError(BasPrintFnT printFn, void *ctx, const char *msg);
|
||||||
static int32_t procIndexForPc(const BasVmT *vm, int32_t pc);
|
static int32_t procIndexForPc(const BasVmT *vm, int32_t pc);
|
||||||
static void reportSnippetResult(BasVmT *vm, BasVmResultE result, BasPrintFnT printFn, void *ctx);
|
static void reportSnippetResult(BasVmT *vm, BasVmResultE result, BasPrintFnT printFn, void *ctx);
|
||||||
static bool tryAssign(BasVmT *vm, const char *line, BasPrintFnT printFn, void *ctx, bool *assigned);
|
static bool tryAssign(BasVmT *vm, const char *line, BasPrintFnT printFn, void *ctx, bool *assigned, const BasDbgPumpT *pump);
|
||||||
|
|
||||||
|
|
||||||
// Evaluate rhs as a value of slot's type and store it there. lhs (lhsLen
|
// Evaluate rhs as a value of slot's type and store it there. lhs (lhsLen
|
||||||
// bytes) names the variable in the confirmation.
|
// bytes) names the variable in the confirmation.
|
||||||
static bool assignSlot(BasVmT *vm, BasValueT *slot, const char *lhs, int32_t lhsLen, const char *rhs, BasPrintFnT printFn, void *ctx) {
|
static bool assignSlot(BasVmT *vm, BasValueT *slot, const char *lhs, int32_t lhsLen, const char *rhs, BasPrintFnT printFn, void *ctx, const BasDbgPumpT *pump) {
|
||||||
// Only scalars can be assigned; the RHS is evaluated as an expression
|
// Only scalars can be assigned; the RHS is evaluated as an expression
|
||||||
bool scalar = slot->type == BAS_TYPE_INTEGER || slot->type == BAS_TYPE_LONG || slot->type == BAS_TYPE_SINGLE || slot->type == BAS_TYPE_DOUBLE || slot->type == BAS_TYPE_STRING || slot->type == BAS_TYPE_BOOLEAN || slot->type == BAS_TYPE_DATE || slot->type == BAS_TYPE_BYTE || slot->type == BAS_TYPE_CURRENCY;
|
bool scalar = slot->type == BAS_TYPE_INTEGER || slot->type == BAS_TYPE_LONG || slot->type == BAS_TYPE_SINGLE || slot->type == BAS_TYPE_DOUBLE || slot->type == BAS_TYPE_STRING || slot->type == BAS_TYPE_BOOLEAN || slot->type == BAS_TYPE_DATE || slot->type == BAS_TYPE_BYTE || slot->type == BAS_TYPE_CURRENCY;
|
||||||
|
|
||||||
|
|
@ -110,7 +110,7 @@ static bool assignSlot(BasVmT *vm, BasValueT *slot, const char *lhs, int32_t lhs
|
||||||
|
|
||||||
BasValueT newVal;
|
BasValueT newVal;
|
||||||
|
|
||||||
if (!evalRhs(vm, rhs, slot->type, &newVal, printFn, ctx)) {
|
if (!evalRhs(vm, rhs, slot->type, &newVal, printFn, ctx, pump)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -197,7 +197,7 @@ int32_t basDbgCurProcIdx(const BasVmT *vm) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
bool basDbgEval(BasVmT *pausedVm, const char *expr, char *out, int32_t outSize) {
|
bool basDbgEval(BasVmT *pausedVm, const char *expr, char *out, int32_t outSize, const BasDbgPumpT *pump) {
|
||||||
const char *end = NULL;
|
const char *end = NULL;
|
||||||
BasValueT *slot = isPaused(pausedVm) ? basDbgResolvePath(pausedVm, dvxSkipWs(expr), &end) : NULL;
|
BasValueT *slot = isPaused(pausedVm) ? basDbgResolvePath(pausedVm, dvxSkipWs(expr), &end) : NULL;
|
||||||
|
|
||||||
|
|
@ -231,7 +231,7 @@ bool basDbgEval(BasVmT *pausedVm, const char *expr, char *out, int32_t outSize)
|
||||||
cap.len = 0;
|
cap.len = 0;
|
||||||
out[0] = '\0';
|
out[0] = '\0';
|
||||||
basVmSetPrintCallback(tvm, capturePrint, &cap);
|
basVmSetPrintCallback(tvm, capturePrint, &cap);
|
||||||
basDbgRunSnippet(tvm);
|
basDbgRunSnippet(tvm, pump);
|
||||||
|
|
||||||
// Strip the trailing newline
|
// Strip the trailing newline
|
||||||
if (cap.len > 0 && out[cap.len - 1] == '\n') {
|
if (cap.len > 0 && out[cap.len - 1] == '\n') {
|
||||||
|
|
@ -244,10 +244,10 @@ bool basDbgEval(BasVmT *pausedVm, const char *expr, char *out, int32_t outSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
bool basDbgExec(BasVmT *pausedVm, const char *line, BasPrintFnT printFn, void *ctx) {
|
bool basDbgExec(BasVmT *pausedVm, const char *line, BasPrintFnT printFn, void *ctx, const BasDbgPumpT *pump) {
|
||||||
bool assigned = false;
|
bool assigned = false;
|
||||||
|
|
||||||
if (!line || *line == '\0' || tryAssign(pausedVm, line, printFn, ctx, &assigned)) {
|
if (!line || *line == '\0' || tryAssign(pausedVm, line, printFn, ctx, &assigned, pump)) {
|
||||||
return assigned;
|
return assigned;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -270,7 +270,7 @@ bool basDbgExec(BasVmT *pausedVm, const char *line, BasPrintFnT printFn, void *c
|
||||||
}
|
}
|
||||||
|
|
||||||
basVmSetPrintCallback(vm, printFn, ctx);
|
basVmSetPrintCallback(vm, printFn, ctx);
|
||||||
reportSnippetResult(vm, basDbgRunSnippet(vm), printFn, ctx);
|
reportSnippetResult(vm, basDbgRunSnippet(vm, pump), printFn, ctx);
|
||||||
basVmDestroy(vm);
|
basVmDestroy(vm);
|
||||||
basModuleFree(mod);
|
basModuleFree(mod);
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -298,12 +298,13 @@ const BasDebugVarT *basDbgFindVar(const BasVmT *vm, const char *name) {
|
||||||
|
|
||||||
void basDbgFormatValue(const BasVmT *vm, const BasValueT *v, char *buf, int32_t bufSize) {
|
void basDbgFormatValue(const BasVmT *vm, const BasValueT *v, char *buf, int32_t bufSize) {
|
||||||
switch (v->type) {
|
switch (v->type) {
|
||||||
|
// Numbers as PRINT and CStr show them
|
||||||
case BAS_TYPE_INTEGER:
|
case BAS_TYPE_INTEGER:
|
||||||
case BAS_TYPE_BYTE:
|
case BAS_TYPE_BYTE:
|
||||||
snprintf(buf, bufSize, "%d", (int)v->intVal);
|
case BAS_TYPE_CURRENCY:
|
||||||
break;
|
case BAS_TYPE_LONG:
|
||||||
|
case BAS_TYPE_SINGLE:
|
||||||
case BAS_TYPE_CURRENCY: {
|
case BAS_TYPE_DOUBLE: {
|
||||||
BasStringT *text = basValFormatString(*v);
|
BasStringT *text = basValFormatString(*v);
|
||||||
|
|
||||||
snprintf(buf, bufSize, "%s", text->data);
|
snprintf(buf, bufSize, "%s", text->data);
|
||||||
|
|
@ -311,18 +312,6 @@ void basDbgFormatValue(const BasVmT *vm, const BasValueT *v, char *buf, int32_t
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case BAS_TYPE_LONG:
|
|
||||||
snprintf(buf, bufSize, "%ld", (long)v->longVal);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case BAS_TYPE_SINGLE:
|
|
||||||
snprintf(buf, bufSize, "%.6g", (double)v->sngVal);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case BAS_TYPE_DOUBLE:
|
|
||||||
snprintf(buf, bufSize, "%.10g", v->dblVal);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case BAS_TYPE_BOOLEAN:
|
case BAS_TYPE_BOOLEAN:
|
||||||
snprintf(buf, bufSize, "%s", v->boolVal ? "True" : "False");
|
snprintf(buf, bufSize, "%s", v->boolVal ? "True" : "False");
|
||||||
break;
|
break;
|
||||||
|
|
@ -584,12 +573,16 @@ BasValueT *basDbgResolvePath(BasVmT *vm, const char *path, const char **endPtr)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
BasVmResultE basDbgRunSnippet(BasVmT *vm) {
|
BasVmResultE basDbgRunSnippet(BasVmT *vm, const BasDbgPumpT *pump) {
|
||||||
BasVmResultE result = BAS_VM_STEP_LIMIT;
|
BasVmResultE result = BAS_VM_STEP_LIMIT;
|
||||||
|
|
||||||
basVmSetStepLimit(vm, DBG_SNIPPET_SLICE);
|
basVmSetStepLimit(vm, DBG_SNIPPET_SLICE);
|
||||||
|
|
||||||
for (int32_t slice = 0; slice < DBG_SNIPPET_SLICES && result == BAS_VM_STEP_LIMIT; slice++) {
|
for (int32_t slice = 0; slice < DBG_SNIPPET_SLICES && result == BAS_VM_STEP_LIMIT; slice++) {
|
||||||
|
if (slice > 0 && pump && pump->fn) {
|
||||||
|
pump->fn(pump->ctx);
|
||||||
|
}
|
||||||
|
|
||||||
result = basVmRun(vm);
|
result = basVmRun(vm);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -597,7 +590,7 @@ BasVmResultE basDbgRunSnippet(BasVmT *vm) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
bool basDbgSetVar(BasVmT *pausedVm, const char *path, const char *expr, BasPrintFnT printFn, void *ctx) {
|
bool basDbgSetVar(BasVmT *pausedVm, const char *path, const char *expr, BasPrintFnT printFn, void *ctx, const BasDbgPumpT *pump) {
|
||||||
const char *start = dvxSkipWs(path);
|
const char *start = dvxSkipWs(path);
|
||||||
const char *end = NULL;
|
const char *end = NULL;
|
||||||
BasValueT *slot = isPaused(pausedVm) ? basDbgResolvePath(pausedVm, start, &end) : NULL;
|
BasValueT *slot = isPaused(pausedVm) ? basDbgResolvePath(pausedVm, start, &end) : NULL;
|
||||||
|
|
@ -607,7 +600,7 @@ bool basDbgSetVar(BasVmT *pausedVm, const char *path, const char *expr, BasPrint
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return assignSlot(pausedVm, slot, start, (int32_t)(end - start), expr, printFn, ctx);
|
return assignSlot(pausedVm, slot, start, (int32_t)(end - start), expr, printFn, ctx, pump);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -811,7 +804,7 @@ static void capturePrint(void *ctx, const char *text, bool newline) {
|
||||||
// Evaluate the right-hand side of an assignment as a BASIC expression
|
// Evaluate the right-hand side of an assignment as a BASIC expression
|
||||||
// (it may use the paused program's variables) converted to targetType.
|
// (it may use the paused program's variables) converted to targetType.
|
||||||
// Reports an error to printFn and returns false.
|
// Reports an error to printFn and returns false.
|
||||||
static bool evalRhs(BasVmT *vm, const char *rhs, uint8_t targetType, BasValueT *outVal, BasPrintFnT printFn, void *ctx) {
|
static bool evalRhs(BasVmT *vm, const char *rhs, uint8_t targetType, BasValueT *outVal, BasPrintFnT printFn, void *ctx, const BasDbgPumpT *pump) {
|
||||||
char src[DBG_RHS_SRC_LEN];
|
char src[DBG_RHS_SRC_LEN];
|
||||||
|
|
||||||
snprintf(src, sizeof(src), "%s %s AS %s: %s = %s", DBG_KW_DIM, DBG_RHS_VAR, basDbgTypeName(targetType), DBG_RHS_VAR, rhs);
|
snprintf(src, sizeof(src), "%s %s AS %s: %s = %s", DBG_KW_DIM, DBG_RHS_VAR, basDbgTypeName(targetType), DBG_RHS_VAR, rhs);
|
||||||
|
|
@ -826,7 +819,7 @@ static bool evalRhs(BasVmT *vm, const char *rhs, uint8_t targetType, BasValueT *
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
BasVmResultE result = basDbgRunSnippet(svm);
|
BasVmResultE result = basDbgRunSnippet(svm, pump);
|
||||||
bool ok = (result == BAS_VM_HALTED || result == BAS_VM_OK) && idx >= 0 && idx < BAS_VM_MAX_GLOBALS;
|
bool ok = (result == BAS_VM_HALTED || result == BAS_VM_OK) && idx >= 0 && idx < BAS_VM_MAX_GLOBALS;
|
||||||
|
|
||||||
if (ok) {
|
if (ok) {
|
||||||
|
|
@ -1002,7 +995,7 @@ static void reportSnippetResult(BasVmT *vm, BasVmResultE result, BasPrintFnT pri
|
||||||
// "var = expr" (LET optional) when paused: assigns to the program's
|
// "var = expr" (LET optional) when paused: assigns to the program's
|
||||||
// variable. Returns true when line was handled as an assignment (*assigned
|
// variable. Returns true when line was handled as an assignment (*assigned
|
||||||
// tells whether it succeeded); false lets the caller run it as a snippet.
|
// tells whether it succeeded); false lets the caller run it as a snippet.
|
||||||
static bool tryAssign(BasVmT *vm, const char *line, BasPrintFnT printFn, void *ctx, bool *assigned) {
|
static bool tryAssign(BasVmT *vm, const char *line, BasPrintFnT printFn, void *ctx, bool *assigned, const BasDbgPumpT *pump) {
|
||||||
if (!isPaused(vm)) {
|
if (!isPaused(vm)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -1034,6 +1027,6 @@ static bool tryAssign(BasVmT *vm, const char *line, BasPrintFnT printFn, void *c
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
*assigned = assignSlot(vm, slot, p, (int32_t)(afterLhs - p), rhs, printFn, ctx);
|
*assigned = assignSlot(vm, slot, p, (int32_t)(afterLhs - p), rhs, printFn, ctx, pump);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -75,18 +75,26 @@ int32_t basDbgCallStack(BasVmT *vm, int32_t curLine, BasDbgRowFnT fn, void *ctx)
|
||||||
// The compiled procedure the paused vm is in, or -1 at module level.
|
// The compiled procedure the paused vm is in, or -1 at module level.
|
||||||
int32_t basDbgCurProcIdx(const BasVmT *vm);
|
int32_t basDbgCurProcIdx(const BasVmT *vm);
|
||||||
|
|
||||||
|
// What a snippet calls between its slices (basDbgRunSnippet), so a long
|
||||||
|
// one lets other work go on: fn(ctx). The calls below take one (NULL:
|
||||||
|
// none).
|
||||||
|
typedef struct {
|
||||||
|
void (*fn)(void *ctx);
|
||||||
|
void *ctx;
|
||||||
|
} BasDbgPumpT;
|
||||||
|
|
||||||
// A watch: a variable path is read directly, anything else is evaluated
|
// A watch: a variable path is read directly, anything else is evaluated
|
||||||
// as an expression in the paused program (pausedVm NULL: fails). out
|
// as an expression in the paused program (pausedVm NULL: fails). out
|
||||||
// receives the value text; false (out undefined) when it cannot be
|
// receives the value text; false (out undefined) when it cannot be
|
||||||
// evaluated.
|
// evaluated.
|
||||||
bool basDbgEval(BasVmT *pausedVm, const char *expr, char *out, int32_t outSize);
|
bool basDbgEval(BasVmT *pausedVm, const char *expr, char *out, int32_t outSize, const BasDbgPumpT *pump);
|
||||||
|
|
||||||
// An Immediate-window line: "var = expr" (LET optional) assigns to the
|
// An Immediate-window line: "var = expr" (LET optional) assigns to the
|
||||||
// paused program's variable; a line starting with PRINT, DIM or LET runs
|
// paused program's variable; a line starting with PRINT, DIM or LET runs
|
||||||
// as it is; anything else is printed (PRINT expr). Everything it shows
|
// as it is; anything else is printed (PRINT expr). Everything it shows
|
||||||
// -- the output, "name = value" after an assignment, "Error: ..." -- goes
|
// -- the output, "name = value" after an assignment, "Error: ..." -- goes
|
||||||
// to printFn. True when a variable of the program was changed.
|
// to printFn. True when a variable of the program was changed.
|
||||||
bool basDbgExec(BasVmT *pausedVm, const char *line, BasPrintFnT printFn, void *ctx);
|
bool basDbgExec(BasVmT *pausedVm, const char *line, BasPrintFnT printFn, void *ctx, const BasDbgPumpT *pump);
|
||||||
|
|
||||||
// The variable called name that is visible where vm is paused, or NULL.
|
// The variable called name that is visible where vm is paused, or NULL.
|
||||||
const BasDebugVarT *basDbgFindVar(const BasVmT *vm, const char *name);
|
const BasDebugVarT *basDbgFindVar(const BasVmT *vm, const char *name);
|
||||||
|
|
@ -117,13 +125,14 @@ const char *basDbgProcNameForPc(const BasVmT *vm, int32_t pc);
|
||||||
BasValueT *basDbgResolvePath(BasVmT *vm, const char *path, const char **endPtr);
|
BasValueT *basDbgResolvePath(BasVmT *vm, const char *path, const char **endPtr);
|
||||||
|
|
||||||
// Run a snippet VM (basDbgSnippetVm) in bounded slices, so an endless
|
// Run a snippet VM (basDbgSnippetVm) in bounded slices, so an endless
|
||||||
// loop cannot hang the host. BAS_VM_STEP_LIMIT means it was abandoned.
|
// loop cannot hang the host, with pump between them. BAS_VM_STEP_LIMIT
|
||||||
BasVmResultE basDbgRunSnippet(BasVmT *vm);
|
// means it was abandoned.
|
||||||
|
BasVmResultE basDbgRunSnippet(BasVmT *vm, const BasDbgPumpT *pump);
|
||||||
|
|
||||||
// Set variable path (see basDbgResolvePath) of the paused program to the
|
// Set variable path (see basDbgResolvePath) of the paused program to the
|
||||||
// value of expr, converted to its type (scalars only). printFn receives
|
// value of expr, converted to its type (scalars only). printFn receives
|
||||||
// "path = value" or why it failed. True when it was set.
|
// "path = value" or why it failed. True when it was set.
|
||||||
bool basDbgSetVar(BasVmT *pausedVm, const char *path, const char *expr, BasPrintFnT printFn, void *ctx);
|
bool basDbgSetVar(BasVmT *pausedVm, const char *path, const char *expr, BasPrintFnT printFn, void *ctx, const BasDbgPumpT *pump);
|
||||||
|
|
||||||
// Compile src as a snippet and return a VM ready to run it; with a
|
// Compile src as a snippet and return a VM ready to run it; with a
|
||||||
// paused VM, its visible variables are the snippet's globals, holding
|
// paused VM, its visible variables are the snippet's globals, holding
|
||||||
|
|
|
||||||
|
|
@ -85,6 +85,7 @@ DVX_ABI_STAMP(basdev);
|
||||||
#define DEV_KIND_AUTO (-1) // CompSourceAdd: the kind follows the file name
|
#define DEV_KIND_AUTO (-1) // CompSourceAdd: the kind follows the file name
|
||||||
#define DEV_NO_INDEX (-1) // no such keyword / file
|
#define DEV_NO_INDEX (-1) // no such keyword / file
|
||||||
#define DEV_TOKEN_ROW_MAX (BAS_MAX_TOKEN_LEN + DEV_MESSAGE_LEN) // one CompTokens$ row
|
#define DEV_TOKEN_ROW_MAX (BAS_MAX_TOKEN_LEN + DEV_MESSAGE_LEN) // one CompTokens$ row
|
||||||
|
#define DEV_YIELD_MS 50 // a compile or a debugger snippet lets the other tasks run this often (devYield)
|
||||||
#define DEV_DESIGN_BUSY "The design UserControls' code is running; try again when it is done."
|
#define DEV_DESIGN_BUSY "The design UserControls' code is running; try again when it is done."
|
||||||
|
|
||||||
// The states RunState reports (debug.bas RUN_*)
|
// The states RunState reports (debug.bas RUN_*)
|
||||||
|
|
@ -184,6 +185,7 @@ static int32_t sCompErrorLine = 0; // concatenated line of the last com
|
||||||
static int32_t sCompErrorCol = 0;
|
static int32_t sCompErrorCol = 0;
|
||||||
static int32_t sCompFileLine = 0; // the same line in its file
|
static int32_t sCompFileLine = 0; // the same line in its file
|
||||||
static char sCompErrorFile[DVX_MAX_PATH];
|
static char sCompErrorFile[DVX_MAX_PATH];
|
||||||
|
static PlatformTicksT sYieldAt = 0; // when devYield last let the other tasks run
|
||||||
static char sDbgMessage[DEV_MESSAGE_LEN]; // the last Dbg* / Run* message
|
static char sDbgMessage[DEV_MESSAGE_LEN]; // the last Dbg* / Run* message
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
@ -253,6 +255,7 @@ static bool devInput(void *ctx, const char *prompt, char *buf, int32_t bu
|
||||||
static void devOnBreak(void *ctx, int32_t line);
|
static void devOnBreak(void *ctx, int32_t line);
|
||||||
static void devPrint(void *ctx, const char *text, bool newline);
|
static void devPrint(void *ctx, const char *text, bool newline);
|
||||||
static const char *devTitle(const DevSourceT *s);
|
static const char *devTitle(const DevSourceT *s);
|
||||||
|
static void devYield(void *ctx);
|
||||||
static int32_t handleNew(DevKindE kind, void *obj);
|
static int32_t handleNew(DevKindE kind, void *obj);
|
||||||
static void *handleOf(int32_t handle, DevKindE kind);
|
static void *handleOf(int32_t handle, DevKindE kind);
|
||||||
static void handleRelease(int32_t idx);
|
static void handleRelease(int32_t idx);
|
||||||
|
|
@ -303,6 +306,9 @@ static void sweepDeferred(void);
|
||||||
static void tokenRow(void *ctx, BasSrcKindE kind, int32_t line, int32_t col, int32_t pos, int32_t len);
|
static void tokenRow(void *ctx, BasSrcKindE kind, int32_t line, int32_t col, int32_t pos, int32_t len);
|
||||||
static void withEventsRow(void *ctx, const char *name, const char *className);
|
static void withEventsRow(void *ctx, const char *name, const char *className);
|
||||||
|
|
||||||
|
// The debugger's snippets let the other tasks run (devYield)
|
||||||
|
static const BasDbgPumpT sDevPump = { devYield, NULL };
|
||||||
|
|
||||||
|
|
||||||
// Shell app-cleanup hook: free what a program that ended left open (its
|
// Shell app-cleanup hook: free what a program that ended left open (its
|
||||||
// code has unwound, so nothing of it is on the stack). Its runtime let
|
// code has unwound, so nothing of it is on the stack). Its runtime let
|
||||||
|
|
@ -465,7 +471,7 @@ int32_t CompCompile(int32_t src, int32_t optionExplicit, int32_t validate) {
|
||||||
}
|
}
|
||||||
|
|
||||||
BasCompileErrorT err;
|
BasCompileErrorT err;
|
||||||
BasModuleT *mod = basBuildCompile(s->ps.source, s->ps.sourceLen, optionExplicit || s->ps.optionExplicit, validate ? &cb : NULL, &v.compiler, &err);
|
BasModuleT *mod = basBuildCompile(s->ps.source, s->ps.sourceLen, optionExplicit || s->ps.optionExplicit, validate ? &cb : NULL, &v.compiler, &err, devYield, NULL);
|
||||||
|
|
||||||
basValidateFree(&v);
|
basValidateFree(&v);
|
||||||
|
|
||||||
|
|
@ -907,7 +913,7 @@ const char *DbgEval(int32_t run, const char *expr) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
return basTextSet(&sText, basDbgEval(r->rt->vm, expr, out, sizeof(out)) ? out : BAS_DBG_EVAL_ERROR);
|
return basTextSet(&sText, basDbgEval(r->rt->vm, expr, out, sizeof(out), &sDevPump) ? out : BAS_DBG_EVAL_ERROR);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -920,7 +926,7 @@ const char *DbgExec(int32_t run, const char *line) {
|
||||||
cap.cap = sizeof(out);
|
cap.cap = sizeof(out);
|
||||||
cap.len = 0;
|
cap.len = 0;
|
||||||
out[0] = '\0';
|
out[0] = '\0';
|
||||||
basDbgExec(r ? r->rt->vm : NULL, line ? line : "", collectPrint, &cap);
|
basDbgExec(r ? r->rt->vm : NULL, line ? line : "", collectPrint, &cap, &sDevPump);
|
||||||
return basTextSet(&sText, out);
|
return basTextSet(&sText, out);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1033,7 +1039,7 @@ int32_t DbgSetVar(int32_t run, const char *path, const char *expr) {
|
||||||
cap.len = 0;
|
cap.len = 0;
|
||||||
sDbgMessage[0] = '\0';
|
sDbgMessage[0] = '\0';
|
||||||
|
|
||||||
bool ok = basDbgSetVar(r ? r->rt->vm : NULL, path ? path : "", expr ? expr : "", collectPrint, &cap);
|
bool ok = basDbgSetVar(r ? r->rt->vm : NULL, path ? path : "", expr ? expr : "", collectPrint, &cap, &sDevPump);
|
||||||
|
|
||||||
dvxTrimRight(sDbgMessage);
|
dvxTrimRight(sDbgMessage);
|
||||||
return ok ? BAS_TRUE : BAS_FALSE;
|
return ok ? BAS_TRUE : BAS_FALSE;
|
||||||
|
|
@ -1111,12 +1117,12 @@ static bool devDoEvents(void *ctx) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (run->parentRt && run->parentRt->eventDepth == 0 && !basFormRtKeepsRunning(run->parentRt)) {
|
if (run->parentRt && !basFormRtCodeOnStack(run->parentRt) && !basFormRtKeepsRunning(run->parentRt)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
run->busy++;
|
run->busy++;
|
||||||
bool ok = dvxUpdate(ac);
|
bool ok = basFormRtUpdate(run->rt);
|
||||||
run->busy--;
|
run->busy--;
|
||||||
|
|
||||||
return ok && !run->stopRequested;
|
return ok && !run->stopRequested;
|
||||||
|
|
@ -1171,6 +1177,19 @@ static const char *devTitle(const DevSourceT *s) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// basBuildCompile's unit callback in CompCompile and the debugger's
|
||||||
|
// snippet pump: every DEV_YIELD_MS the other tasks run (the program's own
|
||||||
|
// events wait, so its sources and its paused run stay as they are).
|
||||||
|
static void devYield(void *ctx) {
|
||||||
|
(void)ctx;
|
||||||
|
|
||||||
|
if (platformClock() - sYieldAt >= PLATFORM_MS_TO_TICKS(DEV_YIELD_MS)) {
|
||||||
|
basFormRtYield();
|
||||||
|
sYieldAt = platformClock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static int32_t handleNew(DevKindE kind, void *obj) {
|
static int32_t handleNew(DevKindE kind, void *obj) {
|
||||||
if (!sCleanupHooked) {
|
if (!sCleanupHooked) {
|
||||||
DevCleanupRegFnT reg = (DevCleanupRegFnT)dlsym(NULL, DVX_SYM(SHELL_CLEANUP_SYM));
|
DevCleanupRegFnT reg = (DevCleanupRegFnT)dlsym(NULL, DVX_SYM(SHELL_CLEANUP_SYM));
|
||||||
|
|
@ -1789,7 +1808,7 @@ void RunFree(int32_t run) {
|
||||||
// Whether the child's code is on the stack (a handler of it, or a call
|
// Whether the child's code is on the stack (a handler of it, or a call
|
||||||
// of this library into it).
|
// of this library into it).
|
||||||
static bool runOnStack(const DevRunT *run) {
|
static bool runOnStack(const DevRunT *run) {
|
||||||
return run->busy > 0 || (run->rt && run->rt->eventDepth > 0);
|
return run->busy > 0 || (run->rt && basFormRtCodeOnStack(run->rt));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -161,8 +161,11 @@ static const BasHelpMapEntryT sHelpMap[] = {
|
||||||
|
|
||||||
// String functions
|
// String functions
|
||||||
{"Asc", "lang.func.string"},
|
{"Asc", "lang.func.string"},
|
||||||
|
{"AscW", "lang.func.string"},
|
||||||
{"Chr", "lang.func.string"},
|
{"Chr", "lang.func.string"},
|
||||||
{"Chr$", "lang.func.string"},
|
{"Chr$", "lang.func.string"},
|
||||||
|
{"ChrW", "lang.func.string"},
|
||||||
|
{"ChrW$", "lang.func.string"},
|
||||||
{"Environ", "lang.func.string"},
|
{"Environ", "lang.func.string"},
|
||||||
{"Environ$", "lang.func.string"},
|
{"Environ$", "lang.func.string"},
|
||||||
{"Format", "lang.func.string"},
|
{"Format", "lang.func.string"},
|
||||||
|
|
@ -224,6 +227,7 @@ static const BasHelpMapEntryT sHelpMap[] = {
|
||||||
{"InputBox", "lang.func.misc"},
|
{"InputBox", "lang.func.misc"},
|
||||||
{"InputBox$", "lang.func.misc"},
|
{"InputBox$", "lang.func.misc"},
|
||||||
{"LoadPicture", "lang.func.misc"},
|
{"LoadPicture", "lang.func.misc"},
|
||||||
|
{"LoadPictureData", "lang.func.misc"},
|
||||||
{"MsgBox", "lang.func.misc"},
|
{"MsgBox", "lang.func.misc"},
|
||||||
|
|
||||||
// SQL functions
|
// SQL functions
|
||||||
|
|
@ -232,12 +236,18 @@ static const BasHelpMapEntryT sHelpMap[] = {
|
||||||
{"SQLEof", "lang.sql"},
|
{"SQLEof", "lang.sql"},
|
||||||
{"SQLError", "lang.sql"},
|
{"SQLError", "lang.sql"},
|
||||||
{"SQLError$", "lang.sql"},
|
{"SQLError$", "lang.sql"},
|
||||||
|
{"SQLEscape", "lang.sql"},
|
||||||
|
{"SQLEscape$", "lang.sql"},
|
||||||
{"SQLExec", "lang.sql"},
|
{"SQLExec", "lang.sql"},
|
||||||
{"SQLField", "lang.sql"},
|
{"SQLField", "lang.sql"},
|
||||||
{"SQLField$", "lang.sql"},
|
{"SQLField$", "lang.sql"},
|
||||||
{"SQLFieldCount", "lang.sql"},
|
{"SQLFieldCount", "lang.sql"},
|
||||||
{"SQLFieldDbl", "lang.sql"},
|
{"SQLFieldDbl", "lang.sql"},
|
||||||
{"SQLFieldInt", "lang.sql"},
|
{"SQLFieldInt", "lang.sql"},
|
||||||
|
{"SQLFieldName", "lang.sql"},
|
||||||
|
{"SQLFieldName$", "lang.sql"},
|
||||||
|
{"SQLFieldText", "lang.sql"},
|
||||||
|
{"SQLFieldText$", "lang.sql"},
|
||||||
{"SQLFreeResult", "lang.sql"},
|
{"SQLFreeResult", "lang.sql"},
|
||||||
{"SQLNext", "lang.sql"},
|
{"SQLNext", "lang.sql"},
|
||||||
{"SQLOpen", "lang.sql"},
|
{"SQLOpen", "lang.sql"},
|
||||||
|
|
|
||||||
|
|
@ -46,9 +46,9 @@
|
||||||
|
|
||||||
.h1 BASIC Development Library
|
.h1 BASIC Development Library
|
||||||
|
|
||||||
basdev.lib is the DVX BASIC compiler and what a development tool builds on it: the build pipeline (basBuild.h), the compile-time Control.Member validator (basValidate.h), the debugger's view of a paused program (basDebug.h), and the SUB / FUNCTION scanner (basProcs.h). The DVX BASIC IDE uses it, and BASIC programs reach it through compiler.bas and debug.bas (basDevBas.c holds their entry points). It also carries the STUB and noicon resources every build inside DVX uses (BAS_BUILD_STUB_CARRIER). It needs basrt.lib, which holds the run host (basRunHost.h) the IDE, the standalone stub, and debug.bas share.
|
basdev.lib is the DVX BASIC compiler and what a development tool builds on it: the build pipeline (basBuild.h), the compile-time Control.Member validator (basValidate.h), the debugger's view of a paused program (basDebug.h), the SUB / FUNCTION scanner (basProcs.h), and the .frm model (frmModel.h) the IDE's designer works on. The DVX BASIC IDE uses it, and BASIC programs reach it through compiler.bas, debug.bas, and designer.bas's Frm* functions (basDevBas.c and frmModelBas.c hold their entry points). It also carries the STUB and noicon resources every build inside DVX uses (BAS_BUILD_STUB_CARRIER). It needs basrt.lib, which holds the run host (basRunHost.h) the IDE, the standalone stub, and debug.bas share.
|
||||||
|
|
||||||
Headers: apps/dvxbasic/basBuild.h, apps/dvxbasic/basdev/basValidate.h, apps/dvxbasic/basdev/basDebug.h, apps/dvxbasic/basdev/basProcs.h, apps/dvxbasic/basdev/basSource.h, apps/dvxbasic/basdev/basHelpMap.h, apps/dvxbasic/basdev/basProject.h, apps/dvxbasic/formrt/basRunHost.h
|
Headers: apps/dvxbasic/basBuild.h, apps/dvxbasic/basdev/basValidate.h, apps/dvxbasic/basdev/basDebug.h, apps/dvxbasic/basdev/basProcs.h, apps/dvxbasic/basdev/basSource.h, apps/dvxbasic/basdev/basHelpMap.h, apps/dvxbasic/basdev/basProject.h, apps/dvxbasic/basdev/frmModel.h, apps/dvxbasic/formrt/basRunHost.h
|
||||||
|
|
||||||
.h2 Compiling (basBuild.h)
|
.h2 Compiling (basBuild.h)
|
||||||
|
|
||||||
|
|
@ -57,7 +57,7 @@ Headers: apps/dvxbasic/basBuild.h, apps/dvxbasic/basdev/basValidate.h, apps/dvxb
|
||||||
-------- -----------
|
-------- -----------
|
||||||
basBuildAddSource Append one file to a BasProjectSourceT the way a project is built: a module in BEGINMODULE, a class module's code in BEGINCLASS, a UserControl's code in BEGINCLASS ... USERCONTROL (its header is kept in ctlTexts), a form's code section in BEGINFORM with its controls (the .frm text is kept in frmTexts), a lone program unwrapped. Records the file in the source map.
|
basBuildAddSource Append one file to a BasProjectSourceT the way a project is built: a module in BEGINMODULE, a class module's code in BEGINCLASS, a UserControl's code in BEGINCLASS ... USERCONTROL (its header is kept in ctlTexts), a form's code section in BEGINFORM with its controls (the .frm text is kept in frmTexts), a lone program unwrapped. Records the file in the source map.
|
||||||
basBuildConcatProject Read every file an open .dbp names (modules, class modules, UserControls, forms) into a BasProjectSourceT through basBuildAddSource.
|
basBuildConcatProject Read every file an open .dbp names (modules, class modules, UserControls, forms) into a BasProjectSourceT through basBuildAddSource.
|
||||||
basBuildCompile Compile source into a BasModuleT with OPTION EXPLICIT and an optional BasCtrlValidatorT. A BasCompileErrorT says why it failed: BAS_COMPILE_ERROR (line, column, message), BAS_COMPILE_NO_MEMORY, or BAS_COMPILE_NO_MODULE.
|
basBuildCompile Compile source into a BasModuleT with OPTION EXPLICIT and an optional BasCtrlValidatorT. A BasCompileErrorT says why it failed: BAS_COMPILE_ERROR (line, column, message), BAS_COMPILE_NO_MEMORY, or BAS_COMPILE_NO_MODULE. An optional unit callback runs after each statement or procedure compiled (basCompilerSetUnitFn), so a long compile can let other work go on.
|
||||||
basBuildErrorText A compiler message without its "Line N, Col M: " prefix.
|
basBuildErrorText A compiler message without its "Line N, Col M: " prefix.
|
||||||
basBuildMapLine Line of the concatenated source to file and file line.
|
basBuildMapLine Line of the concatenated source to file and file line.
|
||||||
basBuildMapFileLine File and file line to the line of the concatenated source (names compared as DOS does: any case, / and \ alike).
|
basBuildMapFileLine File and file line to the line of the concatenated source (names compared as DOS does: any case, / and \ alike).
|
||||||
|
|
@ -74,7 +74,7 @@ A BasValidatorT (zeroed) collects the forms, controls, and menu items of the .fr
|
||||||
|
|
||||||
.h2 Debugging (basDebug.h)
|
.h2 Debugging (basDebug.h)
|
||||||
|
|
||||||
Every function takes the paused program's VM (or NULL when there is none).
|
Every function takes the paused program's VM (or NULL when there is none). The ones that run a snippet (basDbgEval, basDbgExec, basDbgSetVar, basDbgRunSnippet) take a BasDbgPumpT too (NULL for none): its fn(ctx) runs between the snippet's slices, so a long snippet lets other work go on (the IDE shows a frame with input held off; compiler.bas lets the other tasks run).
|
||||||
|
|
||||||
.table
|
.table
|
||||||
Function Description
|
Function Description
|
||||||
|
|
@ -116,7 +116,7 @@ A UserControl's header (the Begin UserControl block of a .ctl) is read and chang
|
||||||
|
|
||||||
.h2 Running a Program (basRunHost.h, in basrt.lib)
|
.h2 Running a Program (basRunHost.h, in basrt.lib)
|
||||||
|
|
||||||
basRunHostCreate makes the VM and form runtime for a module from a BasRunSpecT: App.Path, App.User (from the program's file: its folder in the user folder), App.Data (the DATA folder beside it), COMMAND$, the print, input, and DoEvents callbacks, the screen window's name, the help file, whether runtime errors show their own box, and whether it is a standalone program (the stub's own: the runtime owns the module, frees it with itself, and is ended through the shell's kill hook, shellRegisterAppKill, when the program is ended by force; the IDE and basdev end their runtimes themselves). basRunHostRegisterFrm registers a .frm text, basRunHostLoadForms loads the forms and shows the startup form, basRunHostSlice runs module code once under the step limit (an error's message and line are always set afterwards), basRunHostAlive says whether the event-loop phase goes on, basRunHostTagBreak puts BAS_RUN_BREAK_TAG on the captions while paused, and basRunHostDestroy ends it. The runtime made inside another program's code does not become the current runtime (basFormRtCurrent / basFormRtSetCurrent); each of these calls makes it current while its code runs.
|
basRunHostCreate makes the VM and form runtime for a module from a BasRunSpecT: App.Path, App.User (from the program's file: its folder in the user folder), App.Data (the DATA folder beside it), COMMAND$, the print, input, and DoEvents callbacks, the screen window's name, the help file, whether runtime errors show their own box, whether it is a standalone program (the stub's own: the runtime owns the module and frees it with itself), and whether it ends with its program (endWithApp: the IDE's runs, on a task of their own). A standalone runtime and one made with endWithApp are ended through the shell's kill hook, shellRegisterAppKill, when the program they run for is ended by force; basdev ends its runs itself. A runtime made on one task and run on another (the IDE makes it in a menu command and runs it on a work task, shellAppTaskStart) is moved with basFormRtAdopt, and basFormRtYield lets the other tasks run once without running any of the program's events. basRunHostRegisterFrm registers a .frm text, basRunHostLoadForms loads the forms and shows the startup form, basRunHostSlice runs module code once under the step limit (an error's message and line are always set afterwards), basRunHostAlive says whether the event-loop phase goes on, basRunHostTagBreak puts BAS_RUN_BREAK_TAG on the captions while paused, and basRunHostDestroy ends it. The runtime made inside another program's code does not become the current runtime (basFormRtCurrent / basFormRtSetCurrent); each of these calls makes it current while its code runs.
|
||||||
|
|
||||||
VM accessors a debugger host uses (vm.h): basVmSetAppPaths, basVmSetBreakpointCallback, basVmSetDebugPaused, basVmResume, basVmStop, basVmClearStepState, basVmGetCurrentLine, basVmGetErrorLine.
|
VM accessors a debugger host uses (vm.h): basVmSetAppPaths, basVmSetBreakpointCallback, basVmSetDebugPaused, basVmResume, basVmStop, basVmClearStepState, basVmGetCurrentLine, basVmGetErrorLine.
|
||||||
|
|
||||||
|
|
@ -125,3 +125,144 @@ VM accessors a debugger host uses (vm.h): basVmSetAppPaths, basVmSetBreakpointCa
|
||||||
compiler.bas and debug.bas declare the entry points (see Include Libraries in the BASIC language reference). Source sets, modules, and runs are handles freed with their program. A run is polled: RunPoll runs the child for a bounded time and never waits. A pause in the child's module code returns RUN_PAUSED; a pause inside one of its event handlers (or its forms' Load code, inside RunStart) waits in place while the child pumps DVX events, so the caller's Timer and menus can step, continue, or stop it; RunPoll called from there returns at once. RunFree while the child's code is on the stack stops it at once and releases it at the next library call after that code has unwound.
|
compiler.bas and debug.bas declare the entry points (see Include Libraries in the BASIC language reference). Source sets, modules, and runs are handles freed with their program. A run is polled: RunPoll runs the child for a bounded time and never waits. A pause in the child's module code returns RUN_PAUSED; a pause inside one of its event handlers (or its forms' Load code, inside RunStart) waits in place while the child pumps DVX events, so the caller's Timer and menus can step, continue, or stop it; RunPoll called from there returns at once. RunFree while the child's code is on the stack stops it at once and releases it at the next library call after that code has unwound.
|
||||||
|
|
||||||
CompDesignControls registers a source set's UserControls for the calling program's design forms (basCtlLoadDesign with its runtime, the current one: a child run's own when the child calls it) and holds the module -- one of the caller's own handles; another program's is refused -- until CompDesignRelease, the next CompDesignControls, or the end of that runtime (appEnded for a program, runDestroyHost for a child run), CompModuleFree meanwhile only marking it.
|
CompDesignControls registers a source set's UserControls for the calling program's design forms (basCtlLoadDesign with its runtime, the current one: a child run's own when the child calls it) and holds the module -- one of the caller's own handles; another program's is refused -- until CompDesignRelease, the next CompDesignControls, or the end of that runtime (appEnded for a program, runDestroyHost for a child run), CompModuleFree meanwhile only marking it.
|
||||||
|
|
||||||
|
.link lib.basdev.frmmodel The .frm Model (frmModel.h)
|
||||||
|
|
||||||
|
.topic lib.basdev.frmmodel
|
||||||
|
.title The .frm Model
|
||||||
|
.toc 1 The .frm Model
|
||||||
|
.index FrmModelT
|
||||||
|
.index FrmCtrlT
|
||||||
|
.index FrmMenuItemT
|
||||||
|
.index frmModelLoad
|
||||||
|
.index frmModelWrite
|
||||||
|
.index frmModelText
|
||||||
|
.index frmModelFromForm
|
||||||
|
.index basFontChoiceOf
|
||||||
|
.index basFontPropText
|
||||||
|
.index Font properties (C)
|
||||||
|
|
||||||
|
.h1 The .frm Model
|
||||||
|
|
||||||
|
A form file as data, with no widgets and no windows: the form's properties, its controls (type, name, control array index, parent, the built-in size and state fields, and every other "Key = Value" pair as text), its menu items, and its code section. frmModelLoad reads .frm text (through frmParse) and frmModelWrite writes the canonical text the DVX BASIC IDE saves. The IDE's designer, properties window, menu editor, and copy and paste all work on this model and keep only their live widgets on the side (each control's userData); designer.bas binds it for BASIC programs (the Frm* functions, basdev/frmModelBas.c).
|
||||||
|
|
||||||
|
Header: apps/dvxbasic/basdev/frmModel.h (part of basdev.lib)
|
||||||
|
|
||||||
|
.h2 Types
|
||||||
|
|
||||||
|
.table
|
||||||
|
Type Description
|
||||||
|
---- -----------
|
||||||
|
FrmModelT A form: name, caption, width / height / left / top, padding, spacing, windowState, layout, helpTopic, contextMenu, centered / autoSize / resizable, controls (stb_ds array of FrmCtrlT pointers in tree order: each control directly followed by its children), menuItems (stb_ds array), props (other runtime form properties that were set: Icon, Tag, MousePointer, the Font properties ...), code (malloc'd, NULL = none), dirty (changed by an edit since it was made or loaded).
|
||||||
|
FrmCtrlT A control: name, typeName, parentName + parentIndex (the parent's control array index, so a child of one member of a container array belongs to that member), index (FRM_NO_INDEX: not in an array), left, top, width (MinWidth), height (MinHeight), maxWidth, maxHeight, weight, dragMode, helpTopic, visible, enabled, props (stb_ds array of FrmPropT: every other key, unquoted), userData (the caller's; the model never reads it and loads, copies, and pastes start it at NULL).
|
||||||
|
FrmMenuItemT A menu item: caption ("-" for a separator), name, shortcut, shortcutText, level (0: a menu-bar menu or a popup; 1: its items; 2+: submenus), id (an editor identity, see frmModelMenuReplace), checked, checkable (Checked was written), radioCheck, enabled, visible (False on a level-0 item: a popup menu).
|
||||||
|
FrmPropT One stored "Key = Value": name, value.
|
||||||
|
FrmIntPropT One built-in integer property (Left, Top, MinWidth alias Width, MinHeight alias Height, MaxWidth, MaxHeight, Weight, DragMode): name, alias, offset into FrmCtrlT, saveRule (FRM_SAVE_ALWAYS / IF_NONZERO / IF_POSITIVE).
|
||||||
|
.endtable
|
||||||
|
|
||||||
|
Only values somebody set are stored and written: a property a control has by default (a PictureBox's BorderStyle, a TextBox's MaxLength) is not in the model and not in the file. Values are written in the canonical form for their widget interface type, or for a key the widget does not know, for their type in the runtime's common property table (STRING quoted, BOOL True / False, INT a number, FLOAT and ENUM unquoted); other keys are written as quoted strings. The limits come from the parser's: FRM_MAX_NEST_DEPTH control levels, FRM_MAX_MENU_LEVEL menu levels, and no property line longer than FRM_MAX_LINE_LEN - 1 characters (the writer fails with FRM_WRITE_LINE_TOO_LONG rather than write one the parser would cut).
|
||||||
|
|
||||||
|
.h2 Loading and Writing
|
||||||
|
|
||||||
|
.table
|
||||||
|
Function Description
|
||||||
|
-------- -----------
|
||||||
|
frmModelNew(name) A blank form with the runtime form's defaults.
|
||||||
|
frmModelLoad(text, len) The model of .frm text; NULL when it is not a form (no Begin Form, frmParse refuses it, two controls share a name and index) or out of memory. A Key = Value line is the innermost open block's, also after the blocks inside it, as the runtime reads it; a child names its container by the container's final Index. Of several OptionButtons of one container saved with Value True, the last keeps it.
|
||||||
|
frmModelFree(m) Frees the model, its controls, and menus.
|
||||||
|
frmModelWrite(m, buf, bufSize) The .frm text into buf: its length, or FRM_WRITE_TOO_LARGE / FRM_WRITE_LINE_TOO_LONG (never a partial text).
|
||||||
|
frmModelWriteSize(m) A buffer size that always holds the text.
|
||||||
|
frmModelText(m, &len) The text in a new malloc'd string; NULL with len set to the FRM_WRITE_* code.
|
||||||
|
frmModelSetCode(m, code) Replaces the code section.
|
||||||
|
frmCodeSection(trailing, len, &outLen) The code section of the text after a form's End (blank lines skipped, the first code line keeps its indent).
|
||||||
|
frmModelFromForm(rt, form) A model of a loaded form as it is now: its properties, its controls in their containers with each property that differs from a new control of the type (compared with one made in a hidden window; the six Font properties of the form and of each control that has a font of its own), its menus; no code. What FormText$ writes.
|
||||||
|
.endtable
|
||||||
|
|
||||||
|
.h2 Properties
|
||||||
|
|
||||||
|
.table
|
||||||
|
Function Description
|
||||||
|
-------- -----------
|
||||||
|
frmModelFormProp(m, name, out, size) A form property as text (an unset design property gives "", "0", or "False"); false for run-time-only ones (Visible, DesignMode, DesignSelection, object properties).
|
||||||
|
frmModelSetFormProp(m, name, value) Sets one from text; Name renames the form; an empty value takes an optional one out.
|
||||||
|
frmCtrlPropText(ctrl, name, out, size) A control property as the control holds it (Name, Index, the built-in integers, HelpTopic, Visible / Enabled unless the widget defines them, the stored ones).
|
||||||
|
frmCtrlSetPropText(ctrl, key, value) Applies one the way the loader does (a VB TabIndex is dropped unless the widget defines it).
|
||||||
|
frmModelSetCtrlProp(m, ctrl, name, value) The same with an editor's rules: Name renames the control array, Index makes an array member, then the OptionButton group rule.
|
||||||
|
frmCtrlGetProp / frmCtrlSetProp / frmCtrlRemoveProp The stored properties.
|
||||||
|
frmIntPropAt / frmIntPropFind / frmIntPropGet / Set The built-in integer table.
|
||||||
|
frmIfaceProp(typeName, propName) The widget interface property of a type, NULL when none.
|
||||||
|
.endtable
|
||||||
|
|
||||||
|
.h2 Font Properties
|
||||||
|
|
||||||
|
FontName, FontSize, FontBold, FontItalic, FontUnderline, and FontStrikethru (BAS_PROP_FONT*) are in both the form table and the common control table. They are one font, a DvxFontChoiceT (dvxDlg.h: family, size in pixels, FONT_STYLE_* flags), and the widget's font (wgtFont, wgtSetFont) is the only place they live: a read gives the font the object draws with (a form's is its root widget's), a write gives it that font with the one part changed. The .frm loader collects a block's Font properties and opens one font when the block's properties are read, so their order in the file does not matter. Helpers in formrt.h, for loaders and designers that hold them as .frm text:
|
||||||
|
|
||||||
|
.table
|
||||||
|
Function Description
|
||||||
|
-------- -----------
|
||||||
|
basFontPropAt(idx) The Font property names in .frm order (FontName, FontSize, then the flags), NULL past the end.
|
||||||
|
basFontPropIs(name) True for a Font property name (any case).
|
||||||
|
basFontChoiceOf(font, out) A BitmapFontT as the properties name it (dvxFontFamilyOf, pixelSize, style).
|
||||||
|
basFontPropText(choice, name, out, size) One property of a choice as .frm text (the family, the size, True / False).
|
||||||
|
basFontPropSetText(choice, name, text) Sets one property of a choice from .frm text.
|
||||||
|
.endtable
|
||||||
|
|
||||||
|
.h2 Editing Controls
|
||||||
|
|
||||||
|
.table
|
||||||
|
Function Description
|
||||||
|
-------- -----------
|
||||||
|
frmModelAddCtrl(m, typeName, name, parent) Adds a control as the last child of parent (NULL: the form); name "" or NULL gives a free one (frmModelAutoName); Caption = its name. NULL when the name is taken or parent cannot hold it.
|
||||||
|
frmModelRemoveCtrl(m, ctrl) Removes ctrl and its subtree.
|
||||||
|
frmModelRenameCtrl(m, oldName, newName) Renames a whole control array; children and DataSource / MasterSource of a renamed Data control follow. Identifier rules are the caller's.
|
||||||
|
frmModelMakeArrayMember(m, ctrl, index) Gives a control array index; its children follow.
|
||||||
|
frmModelMoveCtrl(m, ctrl, before) Moves ctrl with its subtree before a sibling (NULL: after the last).
|
||||||
|
frmModelReparent(m, ctrl, parent) Makes ctrl the last child of another container (refuses cycles, non-containers, and too deep a nesting).
|
||||||
|
frmModelSibling(m, idx, dir) The sibling next to a control (+1 after its subtree, -1 before).
|
||||||
|
frmModelFindCtrl / CtrlPos / FindParent / CtrlDepth Lookups.
|
||||||
|
frmModelIsDescendant / SubtreeEnd / NormalizeOrder The tree order.
|
||||||
|
frmModelNameInUse / AutoName / OptionSelected Names and the OptionButton group rule.
|
||||||
|
frmModelCtrlText(m, ctrl, &len) ctrl and its children as .frm control blocks (a copy for the Clipboard).
|
||||||
|
frmModelCtrlsFromText(text, len) Copied blocks read into a scratch model.
|
||||||
|
frmModelInsertTree(m, src, srcRoot, parent, name, index) Moves a subtree out of the scratch model into m: the root takes name / index (NULL: its own name when free, else a free one); a child whose name is taken gets a free one, the members of one array together; children follow their renamed parents.
|
||||||
|
.endtable
|
||||||
|
|
||||||
|
.h2 Menus
|
||||||
|
|
||||||
|
The frmMenu* functions work on any stb_ds array of FrmMenuItemT (an editor's working copy as well as m->menuItems) and keep the levels a tree.
|
||||||
|
|
||||||
|
.table
|
||||||
|
Function Description
|
||||||
|
-------- -----------
|
||||||
|
frmMenuItemInit(mi) A blank item (enabled, visible, level 0).
|
||||||
|
frmMenuInsert(&items, at, level) Inserts a blank item; the level is clamped so the menus stay a tree.
|
||||||
|
frmMenuRemove(&items, idx) Removes an item with its subtree.
|
||||||
|
frmMenuMove(&items, idx, dir) Moves an item with its subtree past its previous / next sibling; its new position or FRM_NO_INDEX.
|
||||||
|
frmMenuIndent(items, idx, delta) Indents / outdents an item with its subtree (at most one level below the item above, not past FRM_MAX_MENU_LEVEL).
|
||||||
|
frmMenuSubtreeEnd / CheckLevels / StripBlank / AutoName Tree helpers; AutoName makes mnuSep1 ... or "mnu" plus the caption's letters and digits.
|
||||||
|
frmMenuPropText / frmMenuSetPropText Menu properties as text (Checked makes a check item).
|
||||||
|
frmModelMenuValidate(m, items, reason, size) The checks before items replace m's menus (levels; every name set, an identifier as the compiler takes one, not the form's or a control's, unique; every Shortcut a key): the first bad item and the reason.
|
||||||
|
frmModelMenuReplace(m, items) Replaces m's menus by a copy; ContextMenu bindings follow renamed items, matched by id (give every item id = position + 1 before editing).
|
||||||
|
frmModelSetMenuProp(m, idx, key, value) Sets an item's property; a Name rename refuses a taken name and ContextMenu bindings follow.
|
||||||
|
.endtable
|
||||||
|
|
||||||
|
.code
|
||||||
|
// Load a form, rename a control, add a label inside a frame, save.
|
||||||
|
FrmModelT *m = frmModelLoad(text, len);
|
||||||
|
|
||||||
|
if (m) {
|
||||||
|
int32_t at = frmModelFindCtrl(m, "Frame1", FRM_NO_INDEX);
|
||||||
|
FrmCtrlT *frame = (at >= 0) ? m->controls[at] : NULL;
|
||||||
|
FrmCtrlT *lbl = frmModelAddCtrl(m, "Label", "", frame);
|
||||||
|
int32_t outLen = 0;
|
||||||
|
|
||||||
|
frmModelRenameCtrl(m, "Command1", "cmdOk");
|
||||||
|
frmModelSetCtrlProp(m, lbl, "Caption", "Name:");
|
||||||
|
|
||||||
|
char *out = frmModelText(m, &outLen);
|
||||||
|
|
||||||
|
// write out[0 .. outLen) to the file
|
||||||
|
free(out);
|
||||||
|
frmModelFree(m);
|
||||||
|
}
|
||||||
|
.endcode
|
||||||
|
|
|
||||||
|
|
@ -31,11 +31,11 @@
|
||||||
// whether it came from a fresh placement, a loaded file or an edit.
|
// whether it came from a fresh placement, a loaded file or an edit.
|
||||||
|
|
||||||
#include "frmModel.h"
|
#include "frmModel.h"
|
||||||
|
#include "../compiler/lexer.h"
|
||||||
#include "dvxApp.h"
|
#include "dvxApp.h"
|
||||||
#include "stb_ds_wrap.h"
|
#include "stb_ds_wrap.h"
|
||||||
|
|
||||||
#include <inttypes.h>
|
#include <inttypes.h>
|
||||||
#include <stdarg.h>
|
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
|
@ -77,14 +77,6 @@ static const FrmIntPropT sIntProps[] = {
|
||||||
|
|
||||||
#define FRM_INT_PROP_COUNT DVX_ARRAY_LEN(sIntProps)
|
#define FRM_INT_PROP_COUNT DVX_ARRAY_LEN(sIntProps)
|
||||||
|
|
||||||
// Form properties of the runtime's table that are run-time state, never
|
|
||||||
// part of a .frm (besides the object-typed ones).
|
|
||||||
static const char *const sRuntimeOnlyFormProps[] = {
|
|
||||||
BAS_PROP_VISIBLE,
|
|
||||||
BAS_PROP_DESIGNMODE,
|
|
||||||
BAS_PROP_DESIGNSELECTION
|
|
||||||
};
|
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// .frm loading context (used by the loadOn* callbacks)
|
// .frm loading context (used by the loadOn* callbacks)
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
@ -123,9 +115,8 @@ typedef struct {
|
||||||
static void buildPad(int32_t indent, char *pad);
|
static void buildPad(int32_t indent, char *pad);
|
||||||
static bool canTakeSubtree(const FrmModelT *m, const FrmCtrlT *parent, int32_t height);
|
static bool canTakeSubtree(const FrmModelT *m, const FrmCtrlT *parent, int32_t height);
|
||||||
static bool ctrlsAmbiguous(const FrmModelT *m);
|
static bool ctrlsAmbiguous(const FrmModelT *m);
|
||||||
static int32_t emitClampedV(char *buf, int32_t bufSize, int32_t pos, const char *fmt, va_list args);
|
|
||||||
static int32_t emitControlHead(const FrmCtrlT *ctrl, char *buf, int32_t bufSize, int32_t pos, const char *pad);
|
static int32_t emitControlHead(const FrmCtrlT *ctrl, char *buf, int32_t bufSize, int32_t pos, const char *pad);
|
||||||
static int32_t emitIndented(char *buf, int32_t bufSize, int32_t pos, int32_t indent, const char *fmt, ...);
|
static int32_t emitEnd(char *buf, int32_t bufSize, int32_t pos, int32_t indent);
|
||||||
static int32_t emitPropLine(char *buf, int32_t bufSize, int32_t pos, const char *pad, const char *name, uint8_t type, const char *value);
|
static int32_t emitPropLine(char *buf, int32_t bufSize, int32_t pos, const char *pad, const char *name, uint8_t type, const char *value);
|
||||||
static const BasPropDescT *formExtraProp(const char *name);
|
static const BasPropDescT *formExtraProp(const char *name);
|
||||||
static bool formFieldText(const FrmModelT *m, const char *name, char *out, int32_t outSize);
|
static bool formFieldText(const FrmModelT *m, const char *name, char *out, int32_t outSize);
|
||||||
|
|
@ -139,7 +130,6 @@ bool frmCtrlPropText(const FrmCtrlT *ctrl, const char *nam
|
||||||
bool frmCtrlRemoveProp(FrmCtrlT *ctrl, const char *name);
|
bool frmCtrlRemoveProp(FrmCtrlT *ctrl, const char *name);
|
||||||
void frmCtrlSetProp(FrmCtrlT *ctrl, const char *name, const char *value);
|
void frmCtrlSetProp(FrmCtrlT *ctrl, const char *name, const char *value);
|
||||||
void frmCtrlSetPropText(FrmCtrlT *ctrl, const char *key, const char *value);
|
void frmCtrlSetPropText(FrmCtrlT *ctrl, const char *key, const char *value);
|
||||||
int32_t frmEmitClamped(char *buf, int32_t bufSize, int32_t pos, const char *fmt, ...);
|
|
||||||
const WgtPropDescT *frmIfaceProp(const char *typeName, const char *propName);
|
const WgtPropDescT *frmIfaceProp(const char *typeName, const char *propName);
|
||||||
const FrmIntPropT *frmIntPropAt(int32_t idx);
|
const FrmIntPropT *frmIntPropAt(int32_t idx);
|
||||||
const FrmIntPropT *frmIntPropFind(const char *name);
|
const FrmIntPropT *frmIntPropFind(const char *name);
|
||||||
|
|
@ -171,7 +161,7 @@ bool frmModelIsDescendant(const FrmModelT *m, const FrmCtr
|
||||||
FrmModelT *frmModelLoad(const char *text, int32_t len);
|
FrmModelT *frmModelLoad(const char *text, int32_t len);
|
||||||
void frmModelMakeArrayMember(FrmModelT *m, FrmCtrlT *ctrl, int32_t index);
|
void frmModelMakeArrayMember(FrmModelT *m, FrmCtrlT *ctrl, int32_t index);
|
||||||
bool frmModelMenuReplace(FrmModelT *m, const FrmMenuItemT *items);
|
bool frmModelMenuReplace(FrmModelT *m, const FrmMenuItemT *items);
|
||||||
int32_t frmModelMenuValidate(const FrmModelT *m, const FrmMenuItemT *items, FrmIdentFnT isIdent, char *reason, int32_t reasonSize);
|
int32_t frmModelMenuValidate(const FrmModelT *m, const FrmMenuItemT *items, char *reason, int32_t reasonSize);
|
||||||
bool frmModelMoveCtrl(FrmModelT *m, FrmCtrlT *ctrl, FrmCtrlT *before);
|
bool frmModelMoveCtrl(FrmModelT *m, FrmCtrlT *ctrl, FrmCtrlT *before);
|
||||||
bool frmModelNameInUse(const FrmModelT *m, const char *name, const char *exceptName, bool checkMenus);
|
bool frmModelNameInUse(const FrmModelT *m, const char *name, const char *exceptName, bool checkMenus);
|
||||||
FrmModelT *frmModelNew(const char *name);
|
FrmModelT *frmModelNew(const char *name);
|
||||||
|
|
@ -260,36 +250,15 @@ static bool ctrlsAmbiguous(const FrmModelT *m) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static int32_t emitClampedV(char *buf, int32_t bufSize, int32_t pos, const char *fmt, va_list args) {
|
|
||||||
if (!buf || pos < 0 || pos >= bufSize) {
|
|
||||||
return pos;
|
|
||||||
}
|
|
||||||
|
|
||||||
int32_t avail = bufSize - pos;
|
|
||||||
int32_t written = (int32_t)vsnprintf(buf + pos, (size_t)avail, fmt, args);
|
|
||||||
|
|
||||||
if (written < 0) {
|
|
||||||
return pos;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cut: vsnprintf wrote avail - 1 characters and the NUL
|
|
||||||
if (written >= avail) {
|
|
||||||
return bufSize - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return pos + written;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// "Begin Type Name" and every property line of one control, without the
|
// "Begin Type Name" and every property line of one control, without the
|
||||||
// closing End so the writer can nest the children first. Interface-typed
|
// closing End so the writer can nest the children first. Interface-typed
|
||||||
// keys and the runtime's common properties are written by their type;
|
// keys and the runtime's common properties are written by their type;
|
||||||
// only other keys go out as quoted strings.
|
// only other keys go out as quoted strings.
|
||||||
static int32_t emitControlHead(const FrmCtrlT *ctrl, char *buf, int32_t bufSize, int32_t pos, const char *pad) {
|
static int32_t emitControlHead(const FrmCtrlT *ctrl, char *buf, int32_t bufSize, int32_t pos, const char *pad) {
|
||||||
pos = frmEmitClamped(buf, bufSize, pos, "%sBegin %s %s\n", pad, ctrl->typeName, ctrl->name);
|
pos = dvxStrAppendfCut(buf, bufSize, pos, "%sBegin %s %s\n", pad, ctrl->typeName, ctrl->name);
|
||||||
|
|
||||||
if (ctrl->index >= 0) {
|
if (ctrl->index >= 0) {
|
||||||
pos = frmEmitClamped(buf, bufSize, pos, "%s %s = %" PRId32 "\n", pad, BAS_PROP_INDEX, ctrl->index);
|
pos = dvxStrAppendfCut(buf, bufSize, pos, "%s %s = %" PRId32 "\n", pad, BAS_PROP_INDEX, ctrl->index);
|
||||||
}
|
}
|
||||||
|
|
||||||
const char *caption = frmCtrlGetProp(ctrl, BAS_PROP_CAPTION);
|
const char *caption = frmCtrlGetProp(ctrl, BAS_PROP_CAPTION);
|
||||||
|
|
@ -315,7 +284,7 @@ static int32_t emitControlHead(const FrmCtrlT *ctrl, char *buf, int32_t bufSize,
|
||||||
}
|
}
|
||||||
|
|
||||||
if (emit) {
|
if (emit) {
|
||||||
pos = frmEmitClamped(buf, bufSize, pos, "%s %s = %" PRId32 "\n", pad, ip->name, val);
|
pos = dvxStrAppendfCut(buf, bufSize, pos, "%s %s = %" PRId32 "\n", pad, ip->name, val);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -360,17 +329,12 @@ static int32_t emitControlHead(const FrmCtrlT *ctrl, char *buf, int32_t bufSize,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// One formatted line indented to an indent-level block.
|
// The End line closing an indent-level block.
|
||||||
static int32_t emitIndented(char *buf, int32_t bufSize, int32_t pos, int32_t indent, const char *fmt, ...) {
|
static int32_t emitEnd(char *buf, int32_t bufSize, int32_t pos, int32_t indent) {
|
||||||
char pad[FRM_PAD_BUF];
|
char pad[FRM_PAD_BUF];
|
||||||
va_list args;
|
|
||||||
|
|
||||||
buildPad(indent, pad);
|
buildPad(indent, pad);
|
||||||
pos = frmEmitClamped(buf, bufSize, pos, "%s", pad);
|
return dvxStrAppendfCut(buf, bufSize, pos, "%s" FRM_KW_END "\n", pad);
|
||||||
va_start(args, fmt);
|
|
||||||
pos = emitClampedV(buf, bufSize, pos, fmt, args);
|
|
||||||
va_end(args);
|
|
||||||
return frmEmitClamped(buf, bufSize, pos, "\n");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -383,17 +347,17 @@ static int32_t emitPropLine(char *buf, int32_t bufSize, int32_t pos, const char
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case WGT_IFACE_STRING:
|
case WGT_IFACE_STRING:
|
||||||
end = frmEmitClamped(buf, bufSize, pos, "%s %s = \"%s\"\n", pad, name, value);
|
end = dvxStrAppendfCut(buf, bufSize, pos, "%s %s = \"%s\"\n", pad, name, value);
|
||||||
break;
|
break;
|
||||||
case WGT_IFACE_BOOL:
|
case WGT_IFACE_BOOL:
|
||||||
end = frmEmitClamped(buf, bufSize, pos, "%s %s = %s\n", pad, name, frmParseBool(value) ? FRM_TEXT_TRUE : FRM_TEXT_FALSE);
|
end = dvxStrAppendfCut(buf, bufSize, pos, "%s %s = %s\n", pad, name, frmParseBool(value) ? FRM_TEXT_TRUE : FRM_TEXT_FALSE);
|
||||||
break;
|
break;
|
||||||
case WGT_IFACE_INT:
|
case WGT_IFACE_INT:
|
||||||
end = frmEmitClamped(buf, bufSize, pos, "%s %s = %d\n", pad, name, atoi(value));
|
end = dvxStrAppendfCut(buf, bufSize, pos, "%s %s = %d\n", pad, name, atoi(value));
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
// FLOAT and ENUM: decimal text or the value name, unquoted
|
// FLOAT and ENUM: decimal text or the value name, unquoted
|
||||||
end = frmEmitClamped(buf, bufSize, pos, "%s %s = %s\n", pad, name, value);
|
end = dvxStrAppendfCut(buf, bufSize, pos, "%s %s = %s\n", pad, name, value);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -412,16 +376,10 @@ static const BasPropDescT *formExtraProp(const char *name) {
|
||||||
const BasPropDescT *pd = basFormRtFindFormProp(name);
|
const BasPropDescT *pd = basFormRtFindFormProp(name);
|
||||||
char dummy[FRM_INT_TEXT_MAX];
|
char dummy[FRM_INT_TEXT_MAX];
|
||||||
|
|
||||||
if (!pd || !pd->writable || pd->type == BAS_PROP_OBJECT || formFieldText(NULL, pd->name, dummy, sizeof(dummy))) {
|
if (!pd || !pd->writable || pd->type == BAS_PROP_OBJECT || basFormRtFormPropRuntimeOnly(pd->name) || formFieldText(NULL, pd->name, dummy, sizeof(dummy))) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int32_t i = 0; i < DVX_ARRAY_LEN(sRuntimeOnlyFormProps); i++) {
|
|
||||||
if (strcasecmp(pd->name, sRuntimeOnlyFormProps[i]) == 0) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return pd;
|
return pd;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -691,16 +649,6 @@ void frmCtrlSetPropText(FrmCtrlT *ctrl, const char *key, const char *value) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
int32_t frmEmitClamped(char *buf, int32_t bufSize, int32_t pos, const char *fmt, ...) {
|
|
||||||
va_list args;
|
|
||||||
|
|
||||||
va_start(args, fmt);
|
|
||||||
pos = emitClampedV(buf, bufSize, pos, fmt, args);
|
|
||||||
va_end(args);
|
|
||||||
return pos;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
const WgtPropDescT *frmIfaceProp(const char *typeName, const char *propName) {
|
const WgtPropDescT *frmIfaceProp(const char *typeName, const char *propName) {
|
||||||
return wgtIfaceFindProp(basCtlIface(typeName), propName);
|
return wgtIfaceFindProp(basCtlIface(typeName), propName);
|
||||||
}
|
}
|
||||||
|
|
@ -1478,7 +1426,7 @@ bool frmModelMenuReplace(FrmModelT *m, const FrmMenuItemT *items) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
int32_t frmModelMenuValidate(const FrmModelT *m, const FrmMenuItemT *items, FrmIdentFnT isIdent, char *reason, int32_t reasonSize) {
|
int32_t frmModelMenuValidate(const FrmModelT *m, const FrmMenuItemT *items, char *reason, int32_t reasonSize) {
|
||||||
int32_t count = (int32_t)arrlen(items);
|
int32_t count = (int32_t)arrlen(items);
|
||||||
int32_t bad = frmMenuCheckLevels(items);
|
int32_t bad = frmMenuCheckLevels(items);
|
||||||
|
|
||||||
|
|
@ -1497,7 +1445,7 @@ int32_t frmModelMenuValidate(const FrmModelT *m, const FrmMenuItemT *items, FrmI
|
||||||
return i;
|
return i;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isIdent && !isIdent(name)) {
|
if (!basIsValidIdent(name)) {
|
||||||
snprintf(reason, reasonSize, "Menu name must be an identifier (letters, digits, underscores): %s", name);
|
snprintf(reason, reasonSize, "Menu name must be an identifier (letters, digits, underscores): %s", name);
|
||||||
return i;
|
return i;
|
||||||
}
|
}
|
||||||
|
|
@ -1902,9 +1850,9 @@ int32_t frmModelWrite(const FrmModelT *m, char *buf, int32_t bufSize) {
|
||||||
return FRM_WRITE_TOO_LARGE;
|
return FRM_WRITE_TOO_LARGE;
|
||||||
}
|
}
|
||||||
|
|
||||||
int32_t pos = frmEmitClamped(buf, bufSize, 0, FRM_VERSION_LINE);
|
int32_t pos = dvxStrAppendfCut(buf, bufSize, 0, FRM_VERSION_LINE);
|
||||||
|
|
||||||
pos = frmEmitClamped(buf, bufSize, pos, FRM_KW_BEGIN " " FRM_KW_FORM " %s\n", m->name);
|
pos = dvxStrAppendfCut(buf, bufSize, pos, FRM_KW_BEGIN " " FRM_KW_FORM " %s\n", m->name);
|
||||||
|
|
||||||
// Form header in the runtime table's order. Name comes from the
|
// Form header in the runtime table's order. Name comes from the
|
||||||
// Begin line; Left / Top are implied by Centered and Width / Height
|
// Begin line; Left / Top are implied by Centered and Width / Height
|
||||||
|
|
@ -1964,13 +1912,13 @@ int32_t frmModelWrite(const FrmModelT *m, char *buf, int32_t bufSize) {
|
||||||
pos = writeControls(m, parents, buf, bufSize, pos, NULL, 1);
|
pos = writeControls(m, parents, buf, bufSize, pos, NULL, 1);
|
||||||
arrfree(parents);
|
arrfree(parents);
|
||||||
|
|
||||||
pos = frmEmitClamped(buf, bufSize, pos, FRM_KW_END "\n");
|
pos = dvxStrAppendfCut(buf, bufSize, pos, FRM_KW_END "\n");
|
||||||
|
|
||||||
if (pos < 0) {
|
if (pos < 0) {
|
||||||
return pos;
|
return pos;
|
||||||
}
|
}
|
||||||
|
|
||||||
// frmEmitClamped parks pos at bufSize - 1 once the buffer is full, so
|
// dvxStrAppendfCut parks pos at bufSize - 1 once the buffer is full, so
|
||||||
// reaching it means the form text was cut short
|
// reaching it means the form text was cut short
|
||||||
if (pos >= bufSize - 1) {
|
if (pos >= bufSize - 1) {
|
||||||
return FRM_WRITE_TOO_LARGE;
|
return FRM_WRITE_TOO_LARGE;
|
||||||
|
|
@ -2099,12 +2047,15 @@ static void loadOnFormEnd(void *userData, const char *trailingSrc, int32_t trail
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx->m->code = (char *)malloc((size_t)codeLen + 1);
|
char *code = (char *)malloc((size_t)codeLen + 1);
|
||||||
|
|
||||||
if (ctx->m->code) {
|
if (code) {
|
||||||
memcpy(ctx->m->code, codeStart, codeLen);
|
memcpy(code, codeStart, codeLen);
|
||||||
ctx->m->code[codeLen] = '\0';
|
code[codeLen] = '\0';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
free(ctx->m->code);
|
||||||
|
ctx->m->code = code;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -2360,7 +2311,7 @@ static int32_t writeCtrlTree(const FrmModelT *m, FrmCtrlT **parents, const FrmCt
|
||||||
pos = writeControls(m, parents, buf, bufSize, pos, ctrl, indent + 1);
|
pos = writeControls(m, parents, buf, bufSize, pos, ctrl, indent + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
return frmEmitClamped(buf, bufSize, pos, "%s" FRM_KW_END "\n", pad);
|
return dvxStrAppendfCut(buf, bufSize, pos, "%s" FRM_KW_END "\n", pad);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -2378,11 +2329,11 @@ static int32_t writeMenus(const FrmModelT *m, char *buf, int32_t bufSize, int32_
|
||||||
// Close blocks back to this item's level
|
// Close blocks back to this item's level
|
||||||
while (curLevel > mi->level) {
|
while (curLevel > mi->level) {
|
||||||
curLevel--;
|
curLevel--;
|
||||||
pos = emitIndented(buf, bufSize, pos, curLevel + 1, FRM_KW_END);
|
pos = emitEnd(buf, bufSize, pos, curLevel + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
buildPad(mi->level + 1, pad);
|
buildPad(mi->level + 1, pad);
|
||||||
pos = frmEmitClamped(buf, bufSize, pos, "%s" FRM_KW_BEGIN " " FRM_KW_MENU " %s\n", pad, mi->name);
|
pos = dvxStrAppendfCut(buf, bufSize, pos, "%s" FRM_KW_BEGIN " " FRM_KW_MENU " %s\n", pad, mi->name);
|
||||||
pos = emitPropLine(buf, bufSize, pos, pad, BAS_PROP_CAPTION, WGT_IFACE_STRING, mi->caption);
|
pos = emitPropLine(buf, bufSize, pos, pad, BAS_PROP_CAPTION, WGT_IFACE_STRING, mi->caption);
|
||||||
|
|
||||||
if (mi->checked || mi->checkable) {
|
if (mi->checked || mi->checkable) {
|
||||||
|
|
@ -2415,7 +2366,7 @@ static int32_t writeMenus(const FrmModelT *m, char *buf, int32_t bufSize, int32_
|
||||||
|
|
||||||
while (curLevel > 0) {
|
while (curLevel > 0) {
|
||||||
curLevel--;
|
curLevel--;
|
||||||
pos = emitIndented(buf, bufSize, pos, curLevel + 1, FRM_KW_END);
|
pos = emitEnd(buf, bufSize, pos, curLevel + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
return pos;
|
return pos;
|
||||||
|
|
@ -20,7 +20,7 @@
|
||||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||||
// IN THE SOFTWARE.
|
// IN THE SOFTWARE.
|
||||||
|
|
||||||
// frmModel.h -- the .frm document model (BASIC run-time library)
|
// frmModel.h -- the .frm document model (BASIC development library)
|
||||||
//
|
//
|
||||||
// A form file as data, with no widgets and no windows: the form's
|
// A form file as data, with no widgets and no windows: the form's
|
||||||
// properties, its controls (type, name, control array index, parent,
|
// properties, its controls (type, name, control array index, parent,
|
||||||
|
|
@ -44,8 +44,8 @@
|
||||||
#ifndef DVXBASIC_FRMMODEL_H
|
#ifndef DVXBASIC_FRMMODEL_H
|
||||||
#define DVXBASIC_FRMMODEL_H
|
#define DVXBASIC_FRMMODEL_H
|
||||||
|
|
||||||
#include "formrt.h"
|
#include "../formrt/formrt.h"
|
||||||
#include "frmParser.h"
|
#include "../formrt/frmParser.h"
|
||||||
|
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
@ -179,10 +179,6 @@ typedef struct FrmModelT {
|
||||||
bool dirty; // changed by an edit operation since it was made or loaded
|
bool dirty; // changed by an edit operation since it was made or loaded
|
||||||
} FrmModelT;
|
} FrmModelT;
|
||||||
|
|
||||||
// Identifier rule for names (the compiler's basIsValidIdent): the model
|
|
||||||
// lives in the run-time library, which has no compiler.
|
|
||||||
typedef bool (*FrmIdentFnT)(const char *name);
|
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// Text
|
// Text
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
@ -193,10 +189,6 @@ typedef bool (*FrmIdentFnT)(const char *name);
|
||||||
// start and length, or NULL when there is no code.
|
// start and length, or NULL when there is no code.
|
||||||
const char *frmCodeSection(const char *trailingSrc, int32_t trailingLen, int32_t *outLen);
|
const char *frmCodeSection(const char *trailingSrc, int32_t trailingLen, int32_t *outLen);
|
||||||
|
|
||||||
// Clamped formatted append into a fixed buffer: returns pos unchanged once
|
|
||||||
// it has reached bufSize, else the new position, capped at bufSize - 1
|
|
||||||
// when the text was cut.
|
|
||||||
int32_t frmEmitClamped(char *buf, int32_t bufSize, int32_t pos, const char *fmt, ...);
|
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// Model lifecycle and .frm text
|
// Model lifecycle and .frm text
|
||||||
|
|
@ -346,7 +338,7 @@ void frmModelRemoveCtrl(FrmModelT *m, FrmCtrlT *ctrl);
|
||||||
// newName: their children follow, and DataSource / MasterSource that
|
// newName: their children follow, and DataSource / MasterSource that
|
||||||
// named a renamed Data control too. False (nothing changed) when there
|
// named a renamed Data control too. False (nothing changed) when there
|
||||||
// is no such control or newName is in use. Identifier rules are the
|
// is no such control or newName is in use. Identifier rules are the
|
||||||
// caller's (FrmIdentFnT).
|
// caller's (basIsValidIdent).
|
||||||
bool frmModelRenameCtrl(FrmModelT *m, const char *oldName, const char *newName);
|
bool frmModelRenameCtrl(FrmModelT *m, const char *oldName, const char *newName);
|
||||||
|
|
||||||
// Give ctrl control array index index (FRM_NO_INDEX: no array); its
|
// Give ctrl control array index index (FRM_NO_INDEX: no array); its
|
||||||
|
|
@ -457,10 +449,10 @@ bool frmMenuPropText(const FrmMenuItemT *mi, const char *key, char *out, int32_t
|
||||||
bool frmMenuSetPropText(FrmMenuItemT *mi, const char *key, const char *value);
|
bool frmMenuSetPropText(FrmMenuItemT *mi, const char *key, const char *value);
|
||||||
|
|
||||||
// Check a menu array before it replaces m's: the levels, and every name
|
// Check a menu array before it replaces m's: the levels, and every name
|
||||||
// is set, an identifier (isIdent; NULL skips this), not the form's or a
|
// is set, an identifier (the compiler's basIsValidIdent), not the form's or a
|
||||||
// control's, not used twice; every Shortcut is a key. Returns the first
|
// control's, not used twice; every Shortcut is a key. Returns the first
|
||||||
// bad item with the reason in reason, FRM_NO_INDEX when all are good.
|
// bad item with the reason in reason, FRM_NO_INDEX when all are good.
|
||||||
int32_t frmModelMenuValidate(const FrmModelT *m, const FrmMenuItemT *items, FrmIdentFnT isIdent, char *reason, int32_t reasonSize);
|
int32_t frmModelMenuValidate(const FrmModelT *m, const FrmMenuItemT *items, char *reason, int32_t reasonSize);
|
||||||
|
|
||||||
// Replace m's menu items by a copy of items. ContextMenu bindings of the
|
// Replace m's menu items by a copy of items. ContextMenu bindings of the
|
||||||
// form and its controls follow renamed items, matched by id (see
|
// form and its controls follow renamed items, matched by id (see
|
||||||
|
|
@ -24,7 +24,7 @@
|
||||||
// Frm*, FrmCtl*, FrmMenu*, FormText$) and the model of a live form
|
// Frm*, FrmCtl*, FrmMenu*, FormText$) and the model of a live form
|
||||||
// (frmModelFromForm)
|
// (frmModelFromForm)
|
||||||
//
|
//
|
||||||
// Called through DECLARE LIBRARY "basrt", so the signatures use only
|
// Called through DECLARE LIBRARY "basdev", so the signatures use only
|
||||||
// BASIC-marshalable types: strings and LONG in, LONG or STRING out, True
|
// BASIC-marshalable types: strings and LONG in, LONG or STRING out, True
|
||||||
// (-1) / False (0). A model is a handle (1, 2, ...; 0 means none) owned
|
// (-1) / False (0). A model is a handle (1, 2, ...; 0 means none) owned
|
||||||
// by the program that opened it and freed when that program ends.
|
// by the program that opened it and freed when that program ends.
|
||||||
|
|
@ -39,7 +39,6 @@
|
||||||
#include "stb_ds_wrap.h"
|
#include "stb_ds_wrap.h"
|
||||||
#include "dvxBas.h"
|
#include "dvxBas.h"
|
||||||
|
|
||||||
#include <dlfcn.h>
|
|
||||||
#include <inttypes.h>
|
#include <inttypes.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
|
@ -50,16 +49,6 @@
|
||||||
#define FRM_VALUE_MAX FRM_MAX_TEXT
|
#define FRM_VALUE_MAX FRM_MAX_TEXT
|
||||||
#define FRM_SCRATCH_SIZE 64 // the hidden window new controls are made in to read their defaults
|
#define FRM_SCRATCH_SIZE 64 // the hidden window new controls are made in to read their defaults
|
||||||
#define FRM_SCRATCH_TITLE ""
|
#define FRM_SCRATCH_TITLE ""
|
||||||
#define SHELL_CLEANUP_SYM "shellRegisterAppCleanup"
|
|
||||||
|
|
||||||
typedef void (*FrmCleanupFnT)(int32_t appId);
|
|
||||||
typedef void (*FrmCleanupRegFnT)(FrmCleanupFnT fn);
|
|
||||||
|
|
||||||
// One open model and the program it belongs to (m NULL: free slot)
|
|
||||||
typedef struct {
|
|
||||||
FrmModelT *m;
|
|
||||||
int32_t appId;
|
|
||||||
} BasFrmHandleT;
|
|
||||||
|
|
||||||
// Building the model of a live form
|
// Building the model of a live form
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
|
@ -73,15 +62,12 @@ typedef struct {
|
||||||
// Module state
|
// Module state
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
static BasFrmHandleT *sModels = NULL; // stb_ds; handle = index + 1
|
|
||||||
static BasTextT sText; // the last string result
|
static BasTextT sText; // the last string result
|
||||||
static bool sCleanupHooked = false;
|
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// Prototypes
|
// Prototypes
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
static void appEnded(int32_t appId);
|
|
||||||
static BasControlT *ctrlForWidget(const BasFormT *form, const WidgetT *w);
|
static BasControlT *ctrlForWidget(const BasFormT *form, const WidgetT *w);
|
||||||
static FrmCtrlT *ctrlOf(int32_t handle, int32_t i, FrmModelT **outModel);
|
static FrmCtrlT *ctrlOf(int32_t handle, int32_t i, FrmModelT **outModel);
|
||||||
const char *FormText(const char *formName);
|
const char *FormText(const char *formName);
|
||||||
|
|
@ -129,18 +115,12 @@ static void liveMenu(FrmLiveT *live, const MenuT *menu, int32_t level)
|
||||||
static void liveMenuAdd(FrmLiveT *live, const char *name, const char *label, int32_t level);
|
static void liveMenuAdd(FrmLiveT *live, const char *name, const char *label, int32_t level);
|
||||||
static const char *liveMenuName(const BasFormT *form, const MenuT *menu);
|
static const char *liveMenuName(const BasFormT *form, const MenuT *menu);
|
||||||
static void liveMenus(FrmLiveT *live);
|
static void liveMenus(FrmLiveT *live);
|
||||||
|
static void modelFree(void *obj);
|
||||||
static FrmModelT *modelOf(int32_t handle);
|
static FrmModelT *modelOf(int32_t handle);
|
||||||
static void valueText(BasValueT v, uint8_t type, char *out, int32_t outSize);
|
static void valueText(BasValueT v, uint8_t type, char *out, int32_t outSize);
|
||||||
|
|
||||||
|
static BasHandlesT sModels = BAS_HANDLES(modelFree, 0); // the open models
|
||||||
|
|
||||||
// Shell app-cleanup hook: free the models a program that ended left open.
|
|
||||||
static void appEnded(int32_t appId) {
|
|
||||||
for (int32_t i = 0; i < (int32_t)arrlen(sModels); i++) {
|
|
||||||
if (sModels[i].m && sModels[i].appId == appId) {
|
|
||||||
FrmFree(i + 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static BasControlT *ctrlForWidget(const BasFormT *form, const WidgetT *w) {
|
static BasControlT *ctrlForWidget(const BasFormT *form, const WidgetT *w) {
|
||||||
|
|
@ -369,10 +349,7 @@ const char *FrmCtlType(int32_t handle, int32_t i) {
|
||||||
|
|
||||||
|
|
||||||
void FrmFree(int32_t handle) {
|
void FrmFree(int32_t handle) {
|
||||||
if (modelOf(handle)) {
|
basHandleFree(&sModels, handle);
|
||||||
frmModelFree(sModels[handle - 1].m);
|
|
||||||
sModels[handle - 1].m = NULL;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -571,43 +548,16 @@ const char *FrmText(int32_t handle) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// A handle for m (freed when the calling program ends), 0 when m is NULL.
|
// A handle for m (freed when the calling program ends), 0 when m is NULL
|
||||||
|
// (or, m freed, when out of memory).
|
||||||
static int32_t handleNew(FrmModelT *m) {
|
static int32_t handleNew(FrmModelT *m) {
|
||||||
if (!m) {
|
int32_t handle = basHandleAdd(&sModels, m);
|
||||||
return BAS_NO_HANDLE;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!sCleanupHooked) {
|
if (handle == BAS_NO_HANDLE) {
|
||||||
FrmCleanupRegFnT reg = (FrmCleanupRegFnT)dlsym(NULL, DVX_SYM(SHELL_CLEANUP_SYM));
|
|
||||||
|
|
||||||
sCleanupHooked = true;
|
|
||||||
|
|
||||||
if (reg) {
|
|
||||||
reg(appEnded);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int32_t idx = 0;
|
|
||||||
|
|
||||||
while (idx < (int32_t)arrlen(sModels) && sModels[idx].m) {
|
|
||||||
idx++;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (idx == (int32_t)arrlen(sModels)) {
|
|
||||||
BasFrmHandleT blank;
|
|
||||||
|
|
||||||
memset(&blank, 0, sizeof(blank));
|
|
||||||
arrput(sModels, blank);
|
|
||||||
|
|
||||||
if (idx == (int32_t)arrlen(sModels)) {
|
|
||||||
frmModelFree(m);
|
frmModelFree(m);
|
||||||
return BAS_NO_HANDLE;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sModels[idx].m = m;
|
return handle;
|
||||||
sModels[idx].appId = dvxMemAppIdPtr ? *dvxMemAppIdPtr : 0;
|
|
||||||
return idx + 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -691,6 +641,11 @@ static void liveDefaults(FrmLiveT *live, BasControlT *ctrl, FrmCtrlT *fc) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A LoadPictureData picture has no file for the .frm to name
|
||||||
|
if (strcasecmp(p->name, BAS_PROP_PICTURE) == 0 && basPictureIsData(val)) {
|
||||||
|
val[0] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
if (!wgtIfaceGetPropText(iface, dflt, p, old, sizeof(old)) || strcmp(val, old) != 0) {
|
if (!wgtIfaceGetPropText(iface, dflt, p, old, sizeof(old)) || strcmp(val, old) != 0) {
|
||||||
frmCtrlSetProp(fc, p->name, val);
|
frmCtrlSetProp(fc, p->name, val);
|
||||||
}
|
}
|
||||||
|
|
@ -762,9 +717,10 @@ static void liveForm(FrmLiveT *live) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Icon reads as a Picture; the .frm holds its file name
|
// Icon reads as a Picture; the .frm holds its file name (none for a
|
||||||
|
// LoadPictureData picture)
|
||||||
if (strcasecmp(pd->name, BAS_PROP_ICON) == 0) {
|
if (strcasecmp(pd->name, BAS_PROP_ICON) == 0) {
|
||||||
frmModelSetFormProp(live->m, pd->name, form->iconPath);
|
frmModelSetFormProp(live->m, pd->name, basPictureIsData(form->iconPath) ? "" : form->iconPath);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -890,8 +846,13 @@ static void liveMenus(FrmLiveT *live) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void modelFree(void *obj) {
|
||||||
|
frmModelFree((FrmModelT *)obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static FrmModelT *modelOf(int32_t handle) {
|
static FrmModelT *modelOf(int32_t handle) {
|
||||||
return (handle >= 1 && handle <= (int32_t)arrlen(sModels)) ? sModels[handle - 1].m : NULL;
|
return (FrmModelT *)basHandleGet(&sModels, handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -39,7 +39,6 @@ Headers: apps/dvxbasic/runtime/vm.h, apps/dvxbasic/runtime/values.h
|
||||||
|
|
||||||
.link lib.basrt.values Value System (values.h)
|
.link lib.basrt.values Value System (values.h)
|
||||||
.link lib.basrt.vm Virtual Machine (vm.h)
|
.link lib.basrt.vm Virtual Machine (vm.h)
|
||||||
.link lib.basrt.frmmodel The .frm Model (frmModel.h)
|
|
||||||
|
|
||||||
.topic lib.basrt.values
|
.topic lib.basrt.values
|
||||||
.title Value System
|
.title Value System
|
||||||
|
|
@ -675,142 +674,3 @@ typedef struct {
|
||||||
debugUdtDefs UDT type definitions for the debugger.
|
debugUdtDefs UDT type definitions for the debugger.
|
||||||
debugUdtDefCount Number of debug UDT definitions.
|
debugUdtDefCount Number of debug UDT definitions.
|
||||||
.endtable
|
.endtable
|
||||||
|
|
||||||
.topic lib.basrt.frmmodel
|
|
||||||
.title The .frm Model
|
|
||||||
.toc 1 The .frm Model
|
|
||||||
.index FrmModelT
|
|
||||||
.index FrmCtrlT
|
|
||||||
.index FrmMenuItemT
|
|
||||||
.index frmModelLoad
|
|
||||||
.index frmModelWrite
|
|
||||||
.index frmModelText
|
|
||||||
.index frmModelFromForm
|
|
||||||
.index basFontChoiceOf
|
|
||||||
.index basFontPropText
|
|
||||||
.index Font properties (C)
|
|
||||||
|
|
||||||
.h1 The .frm Model
|
|
||||||
|
|
||||||
A form file as data, with no widgets and no windows: the form's properties, its controls (type, name, control array index, parent, the built-in size and state fields, and every other "Key = Value" pair as text), its menu items, and its code section. frmModelLoad reads .frm text (through frmParse) and frmModelWrite writes the canonical text the DVX BASIC IDE saves. The IDE's designer, properties window, menu editor, and copy and paste all work on this model and keep only their live widgets on the side (each control's userData); designer.bas binds it for BASIC programs (the Frm* functions, formrt/frmModelBas.c).
|
|
||||||
|
|
||||||
Header: apps/dvxbasic/formrt/frmModel.h (part of basrt.lib)
|
|
||||||
|
|
||||||
.h2 Types
|
|
||||||
|
|
||||||
.table
|
|
||||||
Type Description
|
|
||||||
---- -----------
|
|
||||||
FrmModelT A form: name, caption, width / height / left / top, padding, spacing, windowState, layout, helpTopic, contextMenu, centered / autoSize / resizable, controls (stb_ds array of FrmCtrlT pointers in tree order: each control directly followed by its children), menuItems (stb_ds array), props (other runtime form properties that were set: Icon, Tag, MousePointer, the Font properties ...), code (malloc'd, NULL = none), dirty (changed by an edit since it was made or loaded).
|
|
||||||
FrmCtrlT A control: name, typeName, parentName + parentIndex (the parent's control array index, so a child of one member of a container array belongs to that member), index (FRM_NO_INDEX: not in an array), left, top, width (MinWidth), height (MinHeight), maxWidth, maxHeight, weight, dragMode, helpTopic, visible, enabled, props (stb_ds array of FrmPropT: every other key, unquoted), userData (the caller's; the model never reads it and loads, copies, and pastes start it at NULL).
|
|
||||||
FrmMenuItemT A menu item: caption ("-" for a separator), name, shortcut, shortcutText, level (0: a menu-bar menu or a popup; 1: its items; 2+: submenus), id (an editor identity, see frmModelMenuReplace), checked, checkable (Checked was written), radioCheck, enabled, visible (False on a level-0 item: a popup menu).
|
|
||||||
FrmPropT One stored "Key = Value": name, value.
|
|
||||||
FrmIntPropT One built-in integer property (Left, Top, MinWidth alias Width, MinHeight alias Height, MaxWidth, MaxHeight, Weight, DragMode): name, alias, offset into FrmCtrlT, saveRule (FRM_SAVE_ALWAYS / IF_NONZERO / IF_POSITIVE).
|
|
||||||
.endtable
|
|
||||||
|
|
||||||
Only values somebody set are stored and written: a property a control has by default (a PictureBox's BorderStyle, a TextBox's MaxLength) is not in the model and not in the file. Values are written in the canonical form for their widget interface type, or for a key the widget does not know, for their type in the runtime's common property table (STRING quoted, BOOL True / False, INT a number, FLOAT and ENUM unquoted); other keys are written as quoted strings. The limits come from the parser's: FRM_MAX_NEST_DEPTH control levels, FRM_MAX_MENU_LEVEL menu levels, and no property line longer than FRM_MAX_LINE_LEN - 1 characters (the writer fails with FRM_WRITE_LINE_TOO_LONG rather than write one the parser would cut).
|
|
||||||
|
|
||||||
.h2 Loading and Writing
|
|
||||||
|
|
||||||
.table
|
|
||||||
Function Description
|
|
||||||
-------- -----------
|
|
||||||
frmModelNew(name) A blank form with the runtime form's defaults.
|
|
||||||
frmModelLoad(text, len) The model of .frm text; NULL when it is not a form (no Begin Form, frmParse refuses it, two controls share a name and index) or out of memory. A Key = Value line is the innermost open block's, also after the blocks inside it, as the runtime reads it; a child names its container by the container's final Index. Of several OptionButtons of one container saved with Value True, the last keeps it.
|
|
||||||
frmModelFree(m) Frees the model, its controls, and menus.
|
|
||||||
frmModelWrite(m, buf, bufSize) The .frm text into buf: its length, or FRM_WRITE_TOO_LARGE / FRM_WRITE_LINE_TOO_LONG (never a partial text).
|
|
||||||
frmModelWriteSize(m) A buffer size that always holds the text.
|
|
||||||
frmModelText(m, &len) The text in a new malloc'd string; NULL with len set to the FRM_WRITE_* code.
|
|
||||||
frmModelSetCode(m, code) Replaces the code section.
|
|
||||||
frmCodeSection(trailing, len, &outLen) The code section of the text after a form's End (blank lines skipped, the first code line keeps its indent).
|
|
||||||
frmModelFromForm(rt, form) A model of a loaded form as it is now: its properties, its controls in their containers with each property that differs from a new control of the type (compared with one made in a hidden window; the six Font properties of the form and of each control that has a font of its own), its menus; no code. What FormText$ writes.
|
|
||||||
.endtable
|
|
||||||
|
|
||||||
.h2 Properties
|
|
||||||
|
|
||||||
.table
|
|
||||||
Function Description
|
|
||||||
-------- -----------
|
|
||||||
frmModelFormProp(m, name, out, size) A form property as text (an unset design property gives "", "0", or "False"); false for run-time-only ones (Visible, DesignMode, DesignSelection, object properties).
|
|
||||||
frmModelSetFormProp(m, name, value) Sets one from text; Name renames the form; an empty value takes an optional one out.
|
|
||||||
frmCtrlPropText(ctrl, name, out, size) A control property as the control holds it (Name, Index, the built-in integers, HelpTopic, Visible / Enabled unless the widget defines them, the stored ones).
|
|
||||||
frmCtrlSetPropText(ctrl, key, value) Applies one the way the loader does (a VB TabIndex is dropped unless the widget defines it).
|
|
||||||
frmModelSetCtrlProp(m, ctrl, name, value) The same with an editor's rules: Name renames the control array, Index makes an array member, then the OptionButton group rule.
|
|
||||||
frmCtrlGetProp / frmCtrlSetProp / frmCtrlRemoveProp The stored properties.
|
|
||||||
frmIntPropAt / frmIntPropFind / frmIntPropGet / Set The built-in integer table.
|
|
||||||
frmIfaceProp(typeName, propName) The widget interface property of a type, NULL when none.
|
|
||||||
.endtable
|
|
||||||
|
|
||||||
.h2 Font Properties
|
|
||||||
|
|
||||||
FontName, FontSize, FontBold, FontItalic, FontUnderline, and FontStrikethru (BAS_PROP_FONT*) are in both the form table and the common control table. They are one font, a DvxFontChoiceT (dvxDlg.h: family, size in pixels, FONT_STYLE_* flags), and the widget's font (wgtFont, wgtSetFont) is the only place they live: a read gives the font the object draws with (a form's is its root widget's), a write gives it that font with the one part changed. The .frm loader collects a block's Font properties and opens one font when the block's properties are read, so their order in the file does not matter. Helpers in formrt.h, for loaders and designers that hold them as .frm text:
|
|
||||||
|
|
||||||
.table
|
|
||||||
Function Description
|
|
||||||
-------- -----------
|
|
||||||
basFontPropAt(idx) The Font property names in .frm order (FontName, FontSize, then the flags), NULL past the end.
|
|
||||||
basFontPropIs(name) True for a Font property name (any case).
|
|
||||||
basFontChoiceOf(font, out) A BitmapFontT as the properties name it (dvxFontFamilyOf, pixelSize, style).
|
|
||||||
basFontPropText(choice, name, out, size) One property of a choice as .frm text (the family, the size, True / False).
|
|
||||||
basFontPropSetText(choice, name, text) Sets one property of a choice from .frm text.
|
|
||||||
.endtable
|
|
||||||
|
|
||||||
.h2 Editing Controls
|
|
||||||
|
|
||||||
.table
|
|
||||||
Function Description
|
|
||||||
-------- -----------
|
|
||||||
frmModelAddCtrl(m, typeName, name, parent) Adds a control as the last child of parent (NULL: the form); name "" or NULL gives a free one (frmModelAutoName); Caption = its name. NULL when the name is taken or parent cannot hold it.
|
|
||||||
frmModelRemoveCtrl(m, ctrl) Removes ctrl and its subtree.
|
|
||||||
frmModelRenameCtrl(m, oldName, newName) Renames a whole control array; children and DataSource / MasterSource of a renamed Data control follow. Identifier rules are the caller's.
|
|
||||||
frmModelMakeArrayMember(m, ctrl, index) Gives a control array index; its children follow.
|
|
||||||
frmModelMoveCtrl(m, ctrl, before) Moves ctrl with its subtree before a sibling (NULL: after the last).
|
|
||||||
frmModelReparent(m, ctrl, parent) Makes ctrl the last child of another container (refuses cycles, non-containers, and too deep a nesting).
|
|
||||||
frmModelSibling(m, idx, dir) The sibling next to a control (+1 after its subtree, -1 before).
|
|
||||||
frmModelFindCtrl / CtrlPos / FindParent / CtrlDepth Lookups.
|
|
||||||
frmModelIsDescendant / SubtreeEnd / NormalizeOrder The tree order.
|
|
||||||
frmModelNameInUse / AutoName / OptionSelected Names and the OptionButton group rule.
|
|
||||||
frmModelCtrlText(m, ctrl, &len) ctrl and its children as .frm control blocks (a copy for the Clipboard).
|
|
||||||
frmModelCtrlsFromText(text, len) Copied blocks read into a scratch model.
|
|
||||||
frmModelInsertTree(m, src, srcRoot, parent, name, index) Moves a subtree out of the scratch model into m: the root takes name / index (NULL: its own name when free, else a free one); a child whose name is taken gets a free one, the members of one array together; children follow their renamed parents.
|
|
||||||
.endtable
|
|
||||||
|
|
||||||
.h2 Menus
|
|
||||||
|
|
||||||
The frmMenu* functions work on any stb_ds array of FrmMenuItemT (an editor's working copy as well as m->menuItems) and keep the levels a tree.
|
|
||||||
|
|
||||||
.table
|
|
||||||
Function Description
|
|
||||||
-------- -----------
|
|
||||||
frmMenuItemInit(mi) A blank item (enabled, visible, level 0).
|
|
||||||
frmMenuInsert(&items, at, level) Inserts a blank item; the level is clamped so the menus stay a tree.
|
|
||||||
frmMenuRemove(&items, idx) Removes an item with its subtree.
|
|
||||||
frmMenuMove(&items, idx, dir) Moves an item with its subtree past its previous / next sibling; its new position or FRM_NO_INDEX.
|
|
||||||
frmMenuIndent(items, idx, delta) Indents / outdents an item with its subtree (at most one level below the item above, not past FRM_MAX_MENU_LEVEL).
|
|
||||||
frmMenuSubtreeEnd / CheckLevels / StripBlank / AutoName Tree helpers; AutoName makes mnuSep1 ... or "mnu" plus the caption's letters and digits.
|
|
||||||
frmMenuPropText / frmMenuSetPropText Menu properties as text (Checked makes a check item).
|
|
||||||
frmModelMenuValidate(m, items, isIdent, reason, size) The checks before items replace m's menus (levels; every name set, an identifier by isIdent, not the form's or a control's, unique; every Shortcut a key): the first bad item and the reason.
|
|
||||||
frmModelMenuReplace(m, items) Replaces m's menus by a copy; ContextMenu bindings follow renamed items, matched by id (give every item id = position + 1 before editing).
|
|
||||||
frmModelSetMenuProp(m, idx, key, value) Sets an item's property; a Name rename refuses a taken name and ContextMenu bindings follow.
|
|
||||||
.endtable
|
|
||||||
|
|
||||||
.code
|
|
||||||
// Load a form, rename a control, add a label inside a frame, save.
|
|
||||||
FrmModelT *m = frmModelLoad(text, len);
|
|
||||||
|
|
||||||
if (m) {
|
|
||||||
int32_t at = frmModelFindCtrl(m, "Frame1", FRM_NO_INDEX);
|
|
||||||
FrmCtrlT *frame = (at >= 0) ? m->controls[at] : NULL;
|
|
||||||
FrmCtrlT *lbl = frmModelAddCtrl(m, "Label", "", frame);
|
|
||||||
int32_t outLen = 0;
|
|
||||||
|
|
||||||
frmModelRenameCtrl(m, "Command1", "cmdOk");
|
|
||||||
frmModelSetCtrlProp(m, lbl, "Caption", "Name:");
|
|
||||||
|
|
||||||
char *out = frmModelText(m, &outLen);
|
|
||||||
|
|
||||||
// write out[0 .. outLen) to the file
|
|
||||||
free(out);
|
|
||||||
frmModelFree(m);
|
|
||||||
}
|
|
||||||
.endcode
|
|
||||||
|
|
|
||||||
|
|
@ -93,6 +93,7 @@ typedef enum {
|
||||||
#define BAS_EVT_DSGNPROPCHG "DesignPropertyChanged"
|
#define BAS_EVT_DSGNPROPCHG "DesignPropertyChanged"
|
||||||
#define BAS_EVT_LINKCLICK "LinkClick"
|
#define BAS_EVT_LINKCLICK "LinkClick"
|
||||||
#define BAS_EVT_NEWARGS "NewArgs"
|
#define BAS_EVT_NEWARGS "NewArgs"
|
||||||
|
#define BAS_EVT_FULLSCREEN "FullScreen"
|
||||||
|
|
||||||
#define BAS_EVENT_LIST(X) \
|
#define BAS_EVENT_LIST(X) \
|
||||||
X(BAS_EVT_LOAD, BAS_EVT_SCOPE_FORM, "") \
|
X(BAS_EVT_LOAD, BAS_EVT_SCOPE_FORM, "") \
|
||||||
|
|
@ -125,7 +126,8 @@ typedef enum {
|
||||||
X(BAS_EVT_DESIGNKEY, BAS_EVT_SCOPE_FORM, "KeyCode As Integer, Shift As Integer") \
|
X(BAS_EVT_DESIGNKEY, BAS_EVT_SCOPE_FORM, "KeyCode As Integer, Shift As Integer") \
|
||||||
X(BAS_EVT_DSGNPROPCHG, BAS_EVT_SCOPE_FORM, "CtrlName As String, PropertyName As String") \
|
X(BAS_EVT_DSGNPROPCHG, BAS_EVT_SCOPE_FORM, "CtrlName As String, PropertyName As String") \
|
||||||
X(BAS_EVT_LINKCLICK, BAS_EVT_SCOPE_WIDGET, NULL) \
|
X(BAS_EVT_LINKCLICK, BAS_EVT_SCOPE_WIDGET, NULL) \
|
||||||
X(BAS_EVT_NEWARGS, BAS_EVT_SCOPE_FORM, "")
|
X(BAS_EVT_NEWARGS, BAS_EVT_SCOPE_FORM, "") \
|
||||||
|
X(BAS_EVT_FULLSCREEN, BAS_EVT_SCOPE_FORM, "State As Integer")
|
||||||
|
|
||||||
// A UserControl (a widget class written in BASIC, a .ctl file): the name
|
// A UserControl (a widget class written in BASIC, a .ctl file): the name
|
||||||
// of its own object in its code (UserControl.Refresh), the word after the
|
// of its own object in its code (UserControl.Refresh), the word after the
|
||||||
|
|
|
||||||
|
|
@ -322,14 +322,7 @@ void basPatch32(BasCodeGenT *cg, int32_t pos, int32_t val) {
|
||||||
|
|
||||||
// FNV-1a over the constant's bytes (case-sensitive: constants are exact).
|
// FNV-1a over the constant's bytes (case-sensitive: constants are exact).
|
||||||
static uint32_t constantHash(const char *text, int32_t len) {
|
static uint32_t constantHash(const char *text, int32_t len) {
|
||||||
uint32_t h = BAS_FNV1A_OFFSET;
|
return dvxFnvBytes(DVX_FNV_BASIS, text, len);
|
||||||
|
|
||||||
for (int32_t i = 0; i < len; i++) {
|
|
||||||
h ^= (uint32_t)(uint8_t)text[i];
|
|
||||||
h *= BAS_FNV1A_PRIME;
|
|
||||||
}
|
|
||||||
|
|
||||||
return h;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -248,6 +248,11 @@ static const PredefConstT sPredefConsts[] = {
|
||||||
{ "dvxArrowLeft", ScrollArrowLeftE },
|
{ "dvxArrowLeft", ScrollArrowLeftE },
|
||||||
{ "dvxArrowRight", ScrollArrowRightE },
|
{ "dvxArrowRight", ScrollArrowRightE },
|
||||||
|
|
||||||
|
// A form's FullScreen event's State
|
||||||
|
{ "dvxFullScreenEnded", BAS_FULLSCREEN_ENDED },
|
||||||
|
{ "dvxFullScreenActive", BAS_FULLSCREEN_ACTIVE },
|
||||||
|
{ "dvxFullScreenSuspended", BAS_FULLSCREEN_SUSPENDED },
|
||||||
|
|
||||||
// ScrollBarsNeeded's answer
|
// ScrollBarsNeeded's answer
|
||||||
{ "vbSBNone", BAS_SB_NONE },
|
{ "vbSBNone", BAS_SB_NONE },
|
||||||
{ "vbHorizontal", BAS_SB_HORIZONTAL },
|
{ "vbHorizontal", BAS_SB_HORIZONTAL },
|
||||||
|
|
@ -301,6 +306,7 @@ int32_t basCompilerErrorLine(const BasCompilerT *c);
|
||||||
const char *basCompilerFormScope(const BasCompilerT *c);
|
const char *basCompilerFormScope(const BasCompilerT *c);
|
||||||
int32_t basCompilerGlobalSlot(const BasCompilerT *c, const char *name);
|
int32_t basCompilerGlobalSlot(const BasCompilerT *c, const char *name);
|
||||||
void basCompilerSetOptionExplicit(BasCompilerT *c, bool on);
|
void basCompilerSetOptionExplicit(BasCompilerT *c, bool on);
|
||||||
|
void basCompilerSetUnitFn(BasCompilerT *c, void (*fn)(void *ctx), void *ctx);
|
||||||
void basCompilerSetValidator(BasCompilerT *c, const BasCtrlValidatorT *v);
|
void basCompilerSetValidator(BasCompilerT *c, const BasCtrlValidatorT *v);
|
||||||
void basErrorAt(BasCompilerT *c, int32_t line, int32_t col, const char *fmt, ...);
|
void basErrorAt(BasCompilerT *c, int32_t line, int32_t col, const char *fmt, ...);
|
||||||
void basErrorNode(BasCompilerT *c, const BasNodeT *n, const char *fmt, ...);
|
void basErrorNode(BasCompilerT *c, const BasNodeT *n, const char *fmt, ...);
|
||||||
|
|
@ -441,6 +447,10 @@ bool basCompile(BasCompilerT *c) {
|
||||||
arrsetlen(c->symChanges, 0);
|
arrsetlen(c->symChanges, 0);
|
||||||
basResolveUnitEnd(c, unit);
|
basResolveUnitEnd(c, unit);
|
||||||
basArenaRelease(&c->unitArena, mark);
|
basArenaRelease(&c->unitArena, mark);
|
||||||
|
|
||||||
|
if (c->unitFn) {
|
||||||
|
c->unitFn(c->unitCtx);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!c->hasError) {
|
if (!c->hasError) {
|
||||||
|
|
@ -609,6 +619,12 @@ void basCompilerSetOptionExplicit(BasCompilerT *c, bool on) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void basCompilerSetUnitFn(BasCompilerT *c, void (*fn)(void *ctx), void *ctx) {
|
||||||
|
c->unitFn = fn;
|
||||||
|
c->unitCtx = ctx;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
void basCompilerSetValidator(BasCompilerT *c, const BasCtrlValidatorT *v) {
|
void basCompilerSetValidator(BasCompilerT *c, const BasCtrlValidatorT *v) {
|
||||||
c->validator = v;
|
c->validator = v;
|
||||||
}
|
}
|
||||||
|
|
@ -734,7 +750,7 @@ static bool buildClasses(const BasCompilerT *c, BasModuleT *mod) {
|
||||||
m->typedParams |= basConvertsArg(pi->paramTypes[p], pi->paramFlags[p]) ? (1u << p) : 0u;
|
m->typedParams |= basConvertsArg(pi->paramTypes[p], pi->paramFlags[p]) ? (1u << p) : 0u;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pi->paramCount > 0 && !joinParamNames(pi, &m->paramNames)) {
|
if (!joinParamNames(pi, &m->paramNames)) {
|
||||||
basModuleFree(mod);
|
basModuleFree(mod);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -1194,11 +1210,17 @@ static bool globalStartsTyped(const BasSymT *s) {
|
||||||
|
|
||||||
|
|
||||||
// A procedure's parameter names, comma-separated, into a new string
|
// A procedure's parameter names, comma-separated, into a new string
|
||||||
// (*out): what a late-bound call with named arguments reads. False when
|
// (*out; NULL for a procedure without parameters): what a late-bound
|
||||||
// memory runs out.
|
// call with named arguments reads. False when memory runs out.
|
||||||
static bool joinParamNames(const BasProcInfoT *pi, char **out) {
|
static bool joinParamNames(const BasProcInfoT *pi, char **out) {
|
||||||
size_t len = 0;
|
size_t len = 0;
|
||||||
|
|
||||||
|
*out = NULL;
|
||||||
|
|
||||||
|
if (pi->paramCount <= 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
for (int32_t p = 0; p < pi->paramCount; p++) {
|
for (int32_t p = 0; p < pi->paramCount; p++) {
|
||||||
len += strlen(pi->paramNames[p]) + 1;
|
len += strlen(pi->paramNames[p]) + 1;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -118,6 +118,11 @@ int32_t basCompilerGlobalSlot(const BasCompilerT *c, const char *name);
|
||||||
// OPTION EXPLICIT for the whole program (the project setting).
|
// OPTION EXPLICIT for the whole program (the project setting).
|
||||||
void basCompilerSetOptionExplicit(BasCompilerT *c, bool on);
|
void basCompilerSetOptionExplicit(BasCompilerT *c, bool on);
|
||||||
|
|
||||||
|
// fn(ctx) is called after each unit (a statement or procedure) basCompile
|
||||||
|
// compiles, so a caller can let other work go on through a long compile;
|
||||||
|
// NULL calls nothing.
|
||||||
|
void basCompilerSetUnitFn(BasCompilerT *c, void (*fn)(void *ctx), void *ctx);
|
||||||
|
|
||||||
// Attaches the validator (borrowed: it must outlive basCompile).
|
// Attaches the validator (borrowed: it must outlive basCompile).
|
||||||
void basCompilerSetValidator(BasCompilerT *c, const BasCtrlValidatorT *v);
|
void basCompilerSetValidator(BasCompilerT *c, const BasCtrlValidatorT *v);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -314,6 +314,9 @@ struct BasCompilerS {
|
||||||
BasVmT *constVm; // evaluates constant expressions (made on first use)
|
BasVmT *constVm; // evaluates constant expressions (made on first use)
|
||||||
|
|
||||||
const BasCtrlValidatorT *validator;
|
const BasCtrlValidatorT *validator;
|
||||||
|
|
||||||
|
void (*unitFn)(void *ctx); // basCompilerSetUnitFn: after each unit
|
||||||
|
void *unitCtx;
|
||||||
};
|
};
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,8 @@
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <strings.h>
|
#include <strings.h>
|
||||||
|
|
||||||
|
#include "dvxEndian.h"
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// Variable scope tags
|
// Variable scope tags
|
||||||
// Emitted in bytecode (e.g. OP_FOR scopeTag byte) and consumed
|
// Emitted in bytecode (e.g. OP_FOR scopeTag byte) and consumed
|
||||||
|
|
@ -615,7 +617,11 @@ typedef enum {
|
||||||
BAS_FN_QBCOLOR = 84, // QBCOLOR(n) -> QBasic colour n (0..15) as an RGB value
|
BAS_FN_QBCOLOR = 84, // QBCOLOR(n) -> QBasic colour n (0..15) as an RGB value
|
||||||
BAS_FN_NEW_FORM = 85, // New FormName (compiler-generated): (name) -> a new copy of the form (its own window, controls and form-level variables)
|
BAS_FN_NEW_FORM = 85, // New FormName (compiler-generated): (name) -> a new copy of the form (its own window, controls and form-level variables)
|
||||||
BAS_FN_CALLBYNAME = 86, // CALLBYNAME(object, name, callType [, args...]) -> the member's value or result (Empty for vbLet / vbSet)
|
BAS_FN_CALLBYNAME = 86, // CALLBYNAME(object, name, callType [, args...]) -> the member's value or result (Empty for vbLet / vbSet)
|
||||||
BAS_FN_LOADFORMTEXT = 87 // LOADFORMTEXT(frmText) -> a form loaded from .frm text
|
BAS_FN_LOADFORMTEXT = 87, // LOADFORMTEXT(frmText) -> a form loaded from .frm text
|
||||||
|
BAS_FN_CHRW = 88, // CHRW$(code) -> the DVX (code page 437) character for a Unicode code point ("?" when it has none)
|
||||||
|
BAS_FN_ASCW = 89, // ASCW(s) -> the Unicode code point of s's first character
|
||||||
|
BAS_FN_LOADPICTUREDATA = 90, // LOADPICTUREDATA(bytes) -> a Picture object for the bytes of a picture file
|
||||||
|
BAS_FN_FULLSCREEN_STATE = 91 // FULLSCREEN (a function) -> True while the program screen has (or keeps, behind the desktop) the whole screen
|
||||||
} BasBuiltinFnE;
|
} BasBuiltinFnE;
|
||||||
|
|
||||||
// CallByName's call types (VB's vbMethod, vbGet, vbLet, vbSet) and its
|
// CallByName's call types (VB's vbMethod, vbGet, vbLet, vbSet) and its
|
||||||
|
|
@ -698,14 +704,25 @@ typedef enum {
|
||||||
#define BAS_VT_ARRAY 8192
|
#define BAS_VT_ARRAY 8192
|
||||||
|
|
||||||
// The program-screen functions are numbered BAS_FN_CLS through
|
// The program-screen functions are numbered BAS_FN_CLS through
|
||||||
// BAS_FN_LINE_INPUT and BAS_FN_PCOPY through BAS_FN_PMAP (the VM hands
|
// BAS_FN_LINE_INPUT, BAS_FN_PCOPY through BAS_FN_PMAP, BAS_FN_PEN
|
||||||
// them to runtime/screen.c; see basBuiltinIsScreen).
|
// through BAS_FN_PEN_SET, and BAS_FN_FULLSCREEN_STATE (the VM hands them
|
||||||
|
// to runtime/screen.c; see basBuiltinIsScreen).
|
||||||
#define BAS_FN_SCREEN_FIRST BAS_FN_CLS
|
#define BAS_FN_SCREEN_FIRST BAS_FN_CLS
|
||||||
#define BAS_FN_SCREEN_LAST BAS_FN_LINE_INPUT
|
#define BAS_FN_SCREEN_LAST BAS_FN_LINE_INPUT
|
||||||
#define BAS_FN_SCREEN2_FIRST BAS_FN_PCOPY
|
#define BAS_FN_SCREEN2_FIRST BAS_FN_PCOPY
|
||||||
#define BAS_FN_SCREEN2_LAST BAS_FN_PMAP
|
#define BAS_FN_SCREEN2_LAST BAS_FN_PMAP
|
||||||
#define BAS_FN_SCREEN3_FIRST BAS_FN_PEN
|
#define BAS_FN_SCREEN3_FIRST BAS_FN_PEN
|
||||||
#define BAS_FN_SCREEN3_LAST BAS_FN_PEN_SET
|
#define BAS_FN_SCREEN3_LAST BAS_FN_PEN_SET
|
||||||
|
#define BAS_FN_SCREEN4_FIRST BAS_FN_FULLSCREEN_STATE
|
||||||
|
#define BAS_FN_SCREEN4_LAST BAS_FN_FULLSCREEN_STATE
|
||||||
|
|
||||||
|
// A form's FullScreen event's State (dvxFullScreen* in BASIC): the whole
|
||||||
|
// screen is the form's, the desktop is back while another window has the
|
||||||
|
// focus (the form gets it again with the focus), or it is over (no video
|
||||||
|
// mode could be set again).
|
||||||
|
#define BAS_FULLSCREEN_ENDED 0
|
||||||
|
#define BAS_FULLSCREEN_ACTIVE 1
|
||||||
|
#define BAS_FULLSCREEN_SUSPENDED 2
|
||||||
|
|
||||||
// PEN(n) (QBasic's light pen; the mouse on the program screen) and the
|
// PEN(n) (QBasic's light pen; the mouse on the program screen) and the
|
||||||
// PEN statement's argument.
|
// PEN statement's argument.
|
||||||
|
|
@ -935,12 +952,13 @@ static inline bool basBuiltinArity(uint8_t fn, int32_t *minArgs, int32_t *maxArg
|
||||||
switch (fn) {
|
switch (fn) {
|
||||||
case BAS_FN_NOW: case BAS_FN_DATE: case BAS_FN_TIME:
|
case BAS_FN_NOW: case BAS_FN_DATE: case BAS_FN_TIME:
|
||||||
case BAS_FN_INKEY: case BAS_FN_CSRLIN: case BAS_FN_ERR_DESCRIPTION:
|
case BAS_FN_INKEY: case BAS_FN_CSRLIN: case BAS_FN_ERR_DESCRIPTION:
|
||||||
case BAS_FN_ERR_SOURCE:
|
case BAS_FN_ERR_SOURCE: case BAS_FN_FULLSCREEN_STATE:
|
||||||
*minArgs = 0;
|
*minArgs = 0;
|
||||||
*maxArgs = 0;
|
*maxArgs = 0;
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
case BAS_FN_QBCOLOR: case BAS_FN_NEW_FORM: case BAS_FN_LOADFORMTEXT:
|
case BAS_FN_QBCOLOR: case BAS_FN_NEW_FORM: case BAS_FN_LOADFORMTEXT:
|
||||||
|
case BAS_FN_CHRW: case BAS_FN_ASCW: case BAS_FN_LOADPICTUREDATA:
|
||||||
case BAS_FN_STRREVERSE: case BAS_FN_DATEVALUE: case BAS_FN_TIMEVALUE:
|
case BAS_FN_STRREVERSE: case BAS_FN_DATEVALUE: case BAS_FN_TIMEVALUE:
|
||||||
case BAS_FN_YEAR: case BAS_FN_MONTH: case BAS_FN_DAY:
|
case BAS_FN_YEAR: case BAS_FN_MONTH: case BAS_FN_DAY:
|
||||||
case BAS_FN_HOUR: case BAS_FN_MINUTE: case BAS_FN_SECOND:
|
case BAS_FN_HOUR: case BAS_FN_MINUTE: case BAS_FN_SECOND:
|
||||||
|
|
@ -1126,7 +1144,7 @@ static inline bool basBuiltinArity(uint8_t fn, int32_t *minArgs, int32_t *maxArg
|
||||||
|
|
||||||
// True for the program-screen functions (runtime/screen.c).
|
// True for the program-screen functions (runtime/screen.c).
|
||||||
static inline bool basBuiltinIsScreen(uint8_t fn) {
|
static inline bool basBuiltinIsScreen(uint8_t fn) {
|
||||||
return (fn >= BAS_FN_SCREEN_FIRST && fn <= BAS_FN_SCREEN_LAST) || (fn >= BAS_FN_SCREEN2_FIRST && fn <= BAS_FN_SCREEN2_LAST) || (fn >= BAS_FN_SCREEN3_FIRST && fn <= BAS_FN_SCREEN3_LAST);
|
return (fn >= BAS_FN_SCREEN_FIRST && fn <= BAS_FN_SCREEN_LAST) || (fn >= BAS_FN_SCREEN2_FIRST && fn <= BAS_FN_SCREEN2_LAST) || (fn >= BAS_FN_SCREEN3_FIRST && fn <= BAS_FN_SCREEN3_LAST) || (fn >= BAS_FN_SCREEN4_FIRST && fn <= BAS_FN_SCREEN4_LAST);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1369,41 +1387,36 @@ static inline int32_t basOpcodeOperandSize(uint8_t op) {
|
||||||
// ============================================================
|
// ============================================================
|
||||||
//
|
//
|
||||||
// Bytecode is always little-endian regardless of the host; the compiler,
|
// Bytecode is always little-endian regardless of the host; the compiler,
|
||||||
// the compactor and the serializer all go through these.
|
// the compactor and the serializer all go through these (signed and
|
||||||
|
// unsigned forms of dvxEndian.h's loads and stores).
|
||||||
|
|
||||||
static inline int16_t basReadI16LE(const uint8_t *p) {
|
static inline int16_t basReadI16LE(const uint8_t *p) {
|
||||||
return (int16_t)basReadU16LE(p);
|
return (int16_t)dvxGetLe16(p);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static inline int32_t basReadI32LE(const uint8_t *p) {
|
static inline int32_t basReadI32LE(const uint8_t *p) {
|
||||||
return (int32_t)((uint32_t)p[0] | ((uint32_t)p[1] << 8) | ((uint32_t)p[2] << 16) | ((uint32_t)p[3] << 24));
|
return (int32_t)dvxGetLe32(p);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static inline uint16_t basReadU16LE(const uint8_t *p) {
|
static inline uint16_t basReadU16LE(const uint8_t *p) {
|
||||||
return (uint16_t)((uint16_t)p[0] | ((uint16_t)p[1] << 8));
|
return dvxGetLe16(p);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static inline void basWriteI16LE(uint8_t *p, int16_t v) {
|
static inline void basWriteI16LE(uint8_t *p, int16_t v) {
|
||||||
basWriteU16LE(p, (uint16_t)v);
|
dvxPutLe16(p, (uint16_t)v);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static inline void basWriteI32LE(uint8_t *p, int32_t v) {
|
static inline void basWriteI32LE(uint8_t *p, int32_t v) {
|
||||||
uint32_t u = (uint32_t)v;
|
dvxPutLe32(p, (uint32_t)v);
|
||||||
|
|
||||||
p[0] = (uint8_t)(u & 0xFF);
|
|
||||||
p[1] = (uint8_t)((u >> 8) & 0xFF);
|
|
||||||
p[2] = (uint8_t)((u >> 16) & 0xFF);
|
|
||||||
p[3] = (uint8_t)((u >> 24) & 0xFF);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static inline void basWriteU16LE(uint8_t *p, uint16_t v) {
|
static inline void basWriteU16LE(uint8_t *p, uint16_t v) {
|
||||||
p[0] = (uint8_t)(v & 0xFF);
|
dvxPutLe16(p, v);
|
||||||
p[1] = (uint8_t)((v >> 8) & 0xFF);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -98,7 +98,9 @@ static const char *const sArgsWeekdayName[] = { "Weekday", "Abbreviate", "FirstD
|
||||||
static const BasBuiltinT sBuiltins[] = {
|
static const BasBuiltinT sBuiltins[] = {
|
||||||
// String functions
|
// String functions
|
||||||
{"ASC", OP_STR_ASC, 0, 1, 1, BAS_NODE_TYPE_UNKNOWN, BI_PURE, 0, NULL},
|
{"ASC", OP_STR_ASC, 0, 1, 1, BAS_NODE_TYPE_UNKNOWN, BI_PURE, 0, NULL},
|
||||||
|
{"ASCW", OP_BUILTIN, BAS_FN_ASCW, 0, 0, BAS_TYPE_LONG, BI_PURE, 0, NULL},
|
||||||
{"CHR$", OP_STR_CHR, 0, 1, 1, BAS_TYPE_STRING, BI_PURE, 0, NULL},
|
{"CHR$", OP_STR_CHR, 0, 1, 1, BAS_TYPE_STRING, BI_PURE, 0, NULL},
|
||||||
|
{"CHRW$", OP_BUILTIN, BAS_FN_CHRW, 0, 0, BAS_TYPE_STRING, BI_PURE, 0, NULL},
|
||||||
{"COMMAND$", OP_COMMAND, 0, 0, 1, BAS_TYPE_STRING, 0, 0, NULL},
|
{"COMMAND$", OP_COMMAND, 0, 0, 1, BAS_TYPE_STRING, 0, 0, NULL},
|
||||||
{"DATE$", OP_DATE_STR, 0, 0, 0, BAS_TYPE_STRING, 0, 0, NULL},
|
{"DATE$", OP_DATE_STR, 0, 0, 0, BAS_TYPE_STRING, 0, 0, NULL},
|
||||||
{"ENVIRON$", OP_ENVIRON, 0, 1, 1, BAS_TYPE_STRING, 0, 0, NULL},
|
{"ENVIRON$", OP_ENVIRON, 0, 1, 1, BAS_TYPE_STRING, 0, 0, NULL},
|
||||||
|
|
@ -123,6 +125,7 @@ static const BasBuiltinT sBuiltins[] = {
|
||||||
|
|
||||||
// VB's Variant forms of the string functions: Null text gives Null
|
// VB's Variant forms of the string functions: Null text gives Null
|
||||||
{"CHR", OP_STR_CHR, 0, 1, 1, BAS_TYPE_STRING, BI_PURE, 0, NULL},
|
{"CHR", OP_STR_CHR, 0, 1, 1, BAS_TYPE_STRING, BI_PURE, 0, NULL},
|
||||||
|
{"CHRW", OP_BUILTIN, BAS_FN_CHRW, 0, 0, BAS_TYPE_STRING, BI_PURE, 0, NULL},
|
||||||
{"HEX", OP_STR_HEX, 0, 1, 1, BAS_NODE_TYPE_UNKNOWN, BI_PURE, BAS_NULL_ARG_1, NULL},
|
{"HEX", OP_STR_HEX, 0, 1, 1, BAS_NODE_TYPE_UNKNOWN, BI_PURE, BAS_NULL_ARG_1, NULL},
|
||||||
{"LCASE", OP_STR_LCASE, 0, 1, 1, BAS_NODE_TYPE_UNKNOWN, BI_PURE, BAS_NULL_ARG_1, NULL},
|
{"LCASE", OP_STR_LCASE, 0, 1, 1, BAS_NODE_TYPE_UNKNOWN, BI_PURE, BAS_NULL_ARG_1, NULL},
|
||||||
{"LEFT", OP_STR_LEFT, 0, 2, 2, BAS_NODE_TYPE_UNKNOWN, BI_PURE, BAS_NULL_ARG_1, sArgsLeft},
|
{"LEFT", OP_STR_LEFT, 0, 2, 2, BAS_NODE_TYPE_UNKNOWN, BI_PURE, BAS_NULL_ARG_1, sArgsLeft},
|
||||||
|
|
@ -180,6 +183,7 @@ static const BasBuiltinT sBuiltins[] = {
|
||||||
|
|
||||||
// The program screen
|
// The program screen
|
||||||
{"CSRLIN", OP_BUILTIN, BAS_FN_CSRLIN, 0, 0, BAS_NODE_TYPE_UNKNOWN, 0, 0, NULL},
|
{"CSRLIN", OP_BUILTIN, BAS_FN_CSRLIN, 0, 0, BAS_NODE_TYPE_UNKNOWN, 0, 0, NULL},
|
||||||
|
{"FULLSCREEN", OP_BUILTIN, BAS_FN_FULLSCREEN_STATE, 0, 0, BAS_NODE_TYPE_UNKNOWN, 0, 0, NULL},
|
||||||
{"INKEY$", OP_BUILTIN, BAS_FN_INKEY, 0, 0, BAS_TYPE_STRING, 0, 0, NULL},
|
{"INKEY$", OP_BUILTIN, BAS_FN_INKEY, 0, 0, BAS_TYPE_STRING, 0, 0, NULL},
|
||||||
{"PEN", OP_BUILTIN, BAS_FN_PEN, 0, 0, BAS_NODE_TYPE_UNKNOWN, 0, 0, NULL},
|
{"PEN", OP_BUILTIN, BAS_FN_PEN, 0, 0, BAS_NODE_TYPE_UNKNOWN, 0, 0, NULL},
|
||||||
{"PMAP", OP_BUILTIN, BAS_FN_PMAP, 0, 0, BAS_NODE_TYPE_UNKNOWN, 0, 0, NULL},
|
{"PMAP", OP_BUILTIN, BAS_FN_PMAP, 0, 0, BAS_NODE_TYPE_UNKNOWN, 0, 0, NULL},
|
||||||
|
|
@ -245,6 +249,7 @@ static const BasBuiltinT sBuiltins[] = {
|
||||||
{"ISOBJECT", OP_BUILTIN, BAS_FN_ISOBJECT, 0, 0, BAS_NODE_TYPE_UNKNOWN, 0, 0, NULL},
|
{"ISOBJECT", OP_BUILTIN, BAS_FN_ISOBJECT, 0, 0, BAS_NODE_TYPE_UNKNOWN, 0, 0, NULL},
|
||||||
{"LOADFORMTEXT", OP_BUILTIN, BAS_FN_LOADFORMTEXT, 0, 0, BAS_NODE_TYPE_UNKNOWN, 0, 0, NULL},
|
{"LOADFORMTEXT", OP_BUILTIN, BAS_FN_LOADFORMTEXT, 0, 0, BAS_NODE_TYPE_UNKNOWN, 0, 0, NULL},
|
||||||
{"LOADPICTURE", OP_BUILTIN, BAS_FN_LOADPICTURE, 0, 0, BAS_NODE_TYPE_UNKNOWN, 0, 0, NULL},
|
{"LOADPICTURE", OP_BUILTIN, BAS_FN_LOADPICTURE, 0, 0, BAS_NODE_TYPE_UNKNOWN, 0, 0, NULL},
|
||||||
|
{"LOADPICTUREDATA", OP_BUILTIN, BAS_FN_LOADPICTUREDATA, 0, 0, BAS_NODE_TYPE_UNKNOWN, 0, 0, NULL},
|
||||||
{"TYPENAME", OP_BUILTIN, BAS_FN_TYPENAME, 0, 0, BAS_NODE_TYPE_UNKNOWN, 0, 0, NULL},
|
{"TYPENAME", OP_BUILTIN, BAS_FN_TYPENAME, 0, 0, BAS_NODE_TYPE_UNKNOWN, 0, 0, NULL},
|
||||||
{"VARTYPE", OP_BUILTIN, BAS_FN_VARTYPE, 0, 0, BAS_NODE_TYPE_UNKNOWN, 0, 0, NULL}
|
{"VARTYPE", OP_BUILTIN, BAS_FN_VARTYPE, 0, 0, BAS_NODE_TYPE_UNKNOWN, 0, 0, NULL}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -103,11 +103,10 @@ BasSymT *basScopeFindKind(const BasScopeTabT *tab, const char *name, uint8_t kin
|
||||||
|
|
||||||
|
|
||||||
uint32_t basScopeHash(const char *name) {
|
uint32_t basScopeHash(const char *name) {
|
||||||
uint32_t h = BAS_FNV1A_OFFSET;
|
uint32_t h = DVX_FNV_BASIS;
|
||||||
|
|
||||||
for (; *name != '\0'; name++) {
|
for (; *name != '\0'; name++) {
|
||||||
h ^= (uint32_t)(uint8_t)basAsciiUpper(*name);
|
h = dvxFnvByte(h, (uint8_t)basAsciiUpper(*name));
|
||||||
h *= BAS_FNV1A_PRIME;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return h;
|
return h;
|
||||||
|
|
|
||||||
|
|
@ -92,7 +92,7 @@ Every control in DVX BASIC inherits a set of common properties, events, and meth
|
||||||
ToolTipText String R/W Tooltip text shown after the mouse hovers over the control. Empty string or unset = no tooltip.
|
ToolTipText String R/W Tooltip text shown after the mouse hovers over the control. Empty string or unset = no tooltip.
|
||||||
HelpTopic String R/W Help topic opened by F1 while the control has the focus. Empty = the form's HelpTopic. F1 uses the project's help file.
|
HelpTopic String R/W Help topic opened by F1 while the control has the focus. Empty = the form's HelpTopic. F1 uses the project's help file.
|
||||||
DragMode Integer R/W vbManual (0, the default): the program starts a drag with the Drag method. vbAutomatic (1): pressing the left mouse button on the control starts a drag. See Drag and Drop below.
|
DragMode Integer R/W vbManual (0, the default): the program starts a drag with the Drag method. vbAutomatic (1): pressing the left mouse button on the control starts a drag. See Drag and Drop below.
|
||||||
DragIcon Picture R/W Picture shown under the mouse while the control is dragged: a picture file name (BMP, PNG, JPEG, or GIF) or a Picture object from LOADPICTURE (Set works too). Reading it gives a Picture object, which stands for the file name where a value is needed. Empty = a dotted outline the size of the control.
|
DragIcon Picture R/W Picture shown under the mouse while the control is dragged: a picture file name (BMP, PNG, JPEG, or GIF) or a Picture object from LOADPICTURE or LOADPICTUREDATA (Set works too). Reading it gives a Picture object, which stands for the file name where a value is needed. Empty = a dotted outline the size of the control.
|
||||||
Parent Form R The form the control is on (Text1.Parent.Caption).
|
Parent Form R The form the control is on (Text1.Parent.Caption).
|
||||||
Tag String R/W Any text the program wants to keep with the control (DVX does nothing with it), as in Visual Basic. Also in the .frm file.
|
Tag String R/W Any text the program wants to keep with the control (DVX does nothing with it), as in Visual Basic. Also in the .frm file.
|
||||||
MousePointer Integer R/W The mouse pointer shown over the control (and the controls inside it that have none of their own): vbDefault (0, the control's own pointer), vbArrow (1), vbCrosshair (2), vbSizeNESW (6), vbSizeNS (7), vbSizeNWSE (8), vbSizeWE (9), vbHourglass (11), or vbNoDrop (12). A form's MousePointer is used where no control has one, and Screen.MousePointer over every form of the program. Change it in MouseMove to show different pointers over parts of a control. Also in the .frm file; another value is an error.
|
MousePointer Integer R/W The mouse pointer shown over the control (and the controls inside it that have none of their own): vbDefault (0, the control's own pointer), vbArrow (1), vbCrosshair (2), vbSizeNESW (6), vbSizeNS (7), vbSizeNWSE (8), vbSizeWE (9), vbHourglass (11), or vbNoDrop (12). A form's MousePointer is used where no control has one, and Screen.MousePointer over every form of the program. Change it in MouseMove to show different pointers over parts of a control. Also in the .frm file; another value is an error.
|
||||||
|
|
@ -169,7 +169,7 @@ Drop targets only react: nothing is moved or copied for you. Use Source in DragD
|
||||||
|
|
||||||
Note that vbCancel is the MsgBox Cancel value (2), which as a Drag action means vbEndDrag; cancel a drag with Drag 0.
|
Note that vbCancel is the MsgBox Cancel value (2), which as a Drag action means vbEndDrag; cancel a drag with Drag 0.
|
||||||
|
|
||||||
DragIcon takes a file name or a picture loaded with LOADPICTURE: Set Label1.DragIcon = LoadPicture("drag.bmp"). A missing file or one that is not a picture raises its error at LOADPICTURE; a file name that cannot be read when the drag starts shows the outline instead.
|
DragIcon takes a file name or a picture loaded with LOADPICTURE or LOADPICTUREDATA: Set Label1.DragIcon = LoadPicture("drag.bmp"). A missing file or one that is not a picture raises its error at LOADPICTURE; a file name that cannot be read when the drag starts shows the outline instead.
|
||||||
|
|
||||||
.code
|
.code
|
||||||
' Label1 has DragMode = 1 (vbAutomatic) in the form designer.
|
' Label1 has DragMode = 1 (vbAutomatic) in the form designer.
|
||||||
|
|
@ -335,7 +335,7 @@ PaintPicture's opcode says how each copied pixel is combined with the pixel alre
|
||||||
vbTransparentCopy &H2000000 Every pixel of the picture but the key colour: a colour given after the opcode, else the colour of the picture's top-left pixel (DVX only)
|
vbTransparentCopy &H2000000 Every pixel of the picture but the key colour: a colour given after the opcode, else the colour of the picture's top-left pixel (DVX only)
|
||||||
.endtable
|
.endtable
|
||||||
|
|
||||||
vbGrayCopy and vbTransparentCopy copy the picture at its own size (width and height are not used): Picture1.PaintPicture "ship.bmp", x, y, 32, 32, 0, 0, 32, 32, vbTransparentCopy, vbMagenta draws a sprite.
|
vbGrayCopy and vbTransparentCopy copy the picture at its own size (width and height are not used): Picture1.PaintPicture "ship.bmp", x, y, 32, 32, 0, 0, 32, 32, vbTransparentCopy, vbMagenta draws a sprite. A picture file (or a LOADPICTURE picture) drawn again and again, a sprite in a game loop say, is read from the disk only the first time while the program runs and the file does not change (about a megabyte of pictures is kept that way).
|
||||||
|
|
||||||
Any of the 256 raster operations of Windows can be given by its number; the bits work on the pixel values, so with fewer than 32 bits a pixel the results follow the display's colour format.
|
Any of the 256 raster operations of Windows can be given by its number; the bits work on the pixel values, so with fewer than 32 bits a pixel the results follow the display's colour format.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,25 @@
|
||||||
|
# The MIT License (MIT)
|
||||||
|
#
|
||||||
|
# Copyright (C) 2026 Scott Duensing
|
||||||
|
#
|
||||||
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
# of this software and associated documentation files (the "Software"), to
|
||||||
|
# deal in the Software without restriction, including without limitation the
|
||||||
|
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||||
|
# sell copies of the Software, and to permit persons to whom the Software is
|
||||||
|
# furnished to do so, subject to the following conditions:
|
||||||
|
#
|
||||||
|
# The above copyright notice and this permission notice shall be included in
|
||||||
|
# all copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||||
|
# IN THE SOFTWARE.
|
||||||
|
|
||||||
# dvxbasic.res -- Resource manifest for DVX BASIC
|
# dvxbasic.res -- Resource manifest for DVX BASIC
|
||||||
icon32 icon icon32.bmp
|
icon32 icon icon32.bmp
|
||||||
name text "DVX BASIC"
|
name text "DVX BASIC"
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,11 @@
|
||||||
.index vbModal
|
.index vbModal
|
||||||
.index New (forms)
|
.index New (forms)
|
||||||
.index WindowState
|
.index WindowState
|
||||||
|
.index MaxWidth (form)
|
||||||
|
.index MaxHeight (form)
|
||||||
|
.index GameInput
|
||||||
|
.index FullScreen (form)
|
||||||
|
.index dvxFullScreenActive
|
||||||
.index vbMinimized
|
.index vbMinimized
|
||||||
.index NewArgs
|
.index NewArgs
|
||||||
.index COMMAND$ (new arguments)
|
.index COMMAND$ (new arguments)
|
||||||
|
|
@ -88,12 +93,16 @@ The Form is the top-level container representing a DVX window. It is declared in
|
||||||
WindowState Integer 0 vbNormal (0), vbMinimized (1, shown only as an icon at the bottom of the screen), or vbMaximized (2, filling the screen). Set on a hidden form (or in the .frm file), it takes effect when the form is shown; reading it then gives the state the form will have.
|
WindowState Integer 0 vbNormal (0), vbMinimized (1, shown only as an icon at the bottom of the screen), or vbMaximized (2, filling the screen). Set on a hidden form (or in the .frm file), it takes effect when the form is shown; reading it then gives the state the form will have.
|
||||||
Controls Collection (none) The form's controls (read-only), in the order they were made: Me.Controls.Count, Me.Controls(1) (the first), Me.Controls("Text1"), For Each c In Me.Controls. Elements of a control array are in it too but have no name key. Each use makes a new Collection; changing it does not change the form.
|
Controls Collection (none) The form's controls (read-only), in the order they were made: Me.Controls.Count, Me.Controls(1) (the first), Me.Controls("Text1"), For Each c In Me.Controls. Elements of a control array are in it too but have no name key. Each use makes a new Collection; changing it does not change the form.
|
||||||
DesignMode Boolean False True puts the form in design mode, for a form designer: see Designing Forms below. Not in .frm files.
|
DesignMode Boolean False True puts the form in design mode, for a form designer: see Designing Forms below. Not in .frm files.
|
||||||
Icon Picture (DVX's) The picture shown for the window while it is minimized: a picture file name (BMP, PNG, JPEG, or GIF) or a picture from LOADPICTURE (Set works too); "" puts DVX's back. Reading it gives a Picture object. Also in the .frm file. A file that is not a picture is an error.
|
Icon Picture (DVX's) The picture shown for the window while it is minimized: a picture file name (BMP, PNG, JPEG, or GIF) or a picture from LOADPICTURE or LOADPICTUREDATA (Set works too); "" puts DVX's back. Reading it gives a Picture object. Also in the .frm file. A file that is not a picture is an error.
|
||||||
Tag String "" Any text the program wants to keep with the form, as for controls.
|
Tag String "" Any text the program wants to keep with the form, as for controls.
|
||||||
MousePointer Integer 0 The mouse pointer over the form where its controls have none (see MousePointer under Common Properties).
|
MousePointer Integer 0 The mouse pointer over the form where its controls have none (see MousePointer under Common Properties).
|
||||||
FontName String "System" The form's font: the font of every control on it that has none of its own, and of the form's Print and TextWidth. FontSize, FontBold, FontItalic, FontUnderline, and FontStrikethru go with it, as for controls (see Fonts under Common Properties). Also in the .frm file.
|
FontName String "System" The form's font: the font of every control on it that has none of its own, and of the form's Print and TextWidth. FontSize, FontBold, FontItalic, FontUnderline, and FontStrikethru go with it, as for controls (see Fonts under Common Properties). Also in the .frm file.
|
||||||
ActiveControl Control (none) The control of the form that has the keyboard focus (read-only), Nothing when none of them has it: Me.ActiveControl.SelText for an Edit menu's Copy. Screen.ActiveControl is the focused control of the whole program. Not in .frm files.
|
ActiveControl Control (none) The control of the form that has the keyboard focus (read-only), Nothing when none of them has it: Me.ActiveControl.SelText for an Edit menu's Copy. Screen.ActiveControl is the focused control of the whole program. Not in .frm files.
|
||||||
DesignSelection String "" The name of the control (Text1, or Text1(2) in a control array) whose selection handles are drawn while the form is in design mode; "" for none. Not in .frm files.
|
DesignSelection String "" The name of the control (Text1, or Text1(2) in a control array) whose selection handles are drawn while the form is in design mode; "" for none. Not in .frm files.
|
||||||
|
MaxWidth Integer 0 The widest the user can make the window, by dragging or maximizing; 0 is the screen's width. A window wider than a new MaxWidth shrinks to it. Also in the .frm file.
|
||||||
|
MaxHeight Integer 0 The tallest the window can be made, as MaxWidth; 0 is the screen's height.
|
||||||
|
GameInput Boolean False True for a game: while the form has the keyboard, every key goes to it -- none to F1 (help), F10 or Alt (the menu bar), menu shortcuts, or DVX's shortcut keys. Only Alt+Tab, Ctrl+Esc, and Alt+F4 still reach DVX. Also in the .frm file.
|
||||||
|
FullScreen Boolean False True gives the form the whole screen: a video mode as large as the form's inside (its controls and drawing, without the title bar), the form shown on it at the largest whole multiple of its size that fits, centred on black, kept up to date as it changes. The mouse works on it as in the window, and every key goes to it as with GameInput. A hidden form is shown first. When the user switches to another program (Alt+Tab, Ctrl+Esc), or the program shows a message box or another form, the desktop comes back; the form gets the whole screen again with the focus. Reads False when no video mode could be set (the form stays in its window). False gives the desktop back. Run time only; see the FullScreen event.
|
||||||
.endtable
|
.endtable
|
||||||
|
|
||||||
Maximizing a window, or restoring a maximized one, changes its size, so Resize fires. Minimizing does not change the size and fires no Resize. A minimized window keeps its maximized state: set WindowState = vbMaximized to bring it back filling the screen, vbNormal to bring it back at its normal size.
|
Maximizing a window, or restoring a maximized one, changes its size, so Resize fires. Minimizing does not change the size and fires no Resize. A minimized window keeps its maximized state: set WindowState = vbMaximized to bring it back filling the screen, vbNormal to bring it back at its normal size.
|
||||||
|
|
@ -118,6 +127,7 @@ frmTerminal.WindowState = vbMinimized ' starts as an icon
|
||||||
DragDrop Source As Control, X As Integer, Y As Integer Fires when a dragged control is dropped on an empty part of the form. X and Y are where, in the form.
|
DragDrop Source As Control, X As Integer, Y As Integer Fires when a dragged control is dropped on an empty part of the form. X and Y are where, in the form.
|
||||||
DragOver Source As Control, X As Integer, Y As Integer, State As Integer Fires while a dragged control moves over an empty part of the form. State is vbEnter, vbLeave, or vbOver.
|
DragOver Source As Control, X As Integer, Y As Integer, State As Integer Fires while a dragged control moves over an empty part of the form. State is vbEnter, vbLeave, or vbOver.
|
||||||
NewArgs (none) Fires when the running program is handed new arguments: COMMAND$ has changed (see New Arguments below).
|
NewArgs (none) Fires when the running program is handed new arguments: COMMAND$ has changed (see New Arguments below).
|
||||||
|
FullScreen State As Integer With FullScreen True: dvxFullScreenActive (1) when the form has the whole screen (draw anything that must look new then), dvxFullScreenSuspended (2) when the desktop came back while another window has the focus, dvxFullScreenEnded (0) when the form's video mode could not be set again (FullScreen is False now).
|
||||||
DesignMouse Action As Integer, Button As Integer, X As Integer, Y As Integer, CtrlName As String, Handle As Integer In design mode: the mouse was pressed, moved, released, or double-clicked in the form. See Designing Forms below.
|
DesignMouse Action As Integer, Button As Integer, X As Integer, Y As Integer, CtrlName As String, Handle As Integer In design mode: the mouse was pressed, moved, released, or double-clicked in the form. See Designing Forms below.
|
||||||
DesignKey KeyCode As Integer, Shift As Integer In design mode: a key was pressed in the form (KeyCode and Shift as in KeyDown).
|
DesignKey KeyCode As Integer, Shift As Integer In design mode: a key was pressed in the form (KeyCode and Shift as in KeyDown).
|
||||||
DesignPropertyChanged CtrlName As String, PropertyName As String In design mode: the code of a UserControl on the form called PropertyChanged. See Designing Forms below.
|
DesignPropertyChanged CtrlName As String, PropertyName As String In design mode: the code of a UserControl on the form called PropertyChanged. See Designing Forms below.
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,12 @@
|
||||||
#define RUN_HOST_ERR_FORMRT "Out of memory creating the form runtime."
|
#define RUN_HOST_ERR_FORMRT "Out of memory creating the form runtime."
|
||||||
#define RUN_HOST_ERR_OTHER "Runtime error"
|
#define RUN_HOST_ERR_OTHER "Runtime error"
|
||||||
|
|
||||||
// A standalone program's runtime, its module, and the program it runs for
|
// A program's slice also ends at the next BIOS clock tick (about 55 ms)
|
||||||
|
// after it began, however few its steps (basVmSetSliceClock)
|
||||||
|
#define RUN_HOST_SLICE_TICKS 1
|
||||||
|
|
||||||
|
// A runtime ended with its program by force (standalone or endWithApp),
|
||||||
|
// its module when it owns it (standalone), and the program it runs for
|
||||||
typedef struct {
|
typedef struct {
|
||||||
BasFormRtT *rt;
|
BasFormRtT *rt;
|
||||||
BasModuleT *module;
|
BasModuleT *module;
|
||||||
|
|
@ -68,11 +73,13 @@ bool basRunHostAlive(const BasFormRtT *rt) {
|
||||||
|
|
||||||
// The shell's kill hook: a program ended by force has its runtimes ended
|
// The shell's kill hook: a program ended by force has its runtimes ended
|
||||||
// as its own end would (its windows are still there, none of its code
|
// as its own end would (its windows are still there, none of its code
|
||||||
// runs).
|
// runs), except that what it drew for the printer is thrown away, not
|
||||||
|
// printed (that would hold the kill for seconds).
|
||||||
static void basRunHostAppKilled(int32_t appId) {
|
static void basRunHostAppKilled(int32_t appId) {
|
||||||
for (ptrdiff_t i = arrlen(sHosts) - 1; i >= 0; i--) {
|
for (ptrdiff_t i = arrlen(sHosts) - 1; i >= 0; i--) {
|
||||||
if (i < arrlen(sHosts) && sHosts[i].appId == appId) {
|
if (i < arrlen(sHosts) && sHosts[i].appId == appId) {
|
||||||
dvxLog("BASIC: '%s' ended by force; its runtime freed", sHosts[i].rt->title);
|
dvxLog("BASIC: '%s' ended by force; its runtime freed", sHosts[i].rt->title);
|
||||||
|
basFormRtPrinterKill(sHosts[i].rt);
|
||||||
basRunHostDestroy(sHosts[i].rt);
|
basRunHostDestroy(sHosts[i].rt);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -116,6 +123,9 @@ BasFormRtT *basRunHostCreate(AppContextT *ctx, BasModuleT *module, const BasRunS
|
||||||
basVmSetDoEventsCallback(vm, spec->doEventsFn, spec->doEventsCtx);
|
basVmSetDoEventsCallback(vm, spec->doEventsFn, spec->doEventsCtx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
basVmSetSliceClock(vm, platformBiosTicks(), RUN_HOST_SLICE_TICKS);
|
||||||
|
basVmSetMicrosClock(vm, platformMicros);
|
||||||
|
|
||||||
// DECLARE LIBRARY functions
|
// DECLARE LIBRARY functions
|
||||||
BasExternCallbacksT extCb;
|
BasExternCallbacksT extCb;
|
||||||
|
|
||||||
|
|
@ -142,8 +152,8 @@ BasFormRtT *basRunHostCreate(AppContextT *ctx, BasModuleT *module, const BasRunS
|
||||||
snprintf(rt->helpFile, sizeof(rt->helpFile), "%s", spec->helpFile);
|
snprintf(rt->helpFile, sizeof(rt->helpFile), "%s", spec->helpFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (spec->standalone) {
|
if (spec->standalone || spec->endWithApp) {
|
||||||
HostRecT rec = { rt, module, ctx->currentAppId };
|
HostRecT rec = { rt, spec->standalone ? module : NULL, ctx->currentAppId };
|
||||||
|
|
||||||
arrput(sHosts, rec);
|
arrput(sHosts, rec);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,9 @@
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
|
// A program's task stack: the standalone stub's, and the IDE's runs.
|
||||||
|
#define BAS_RUN_STACK_SIZE 65536
|
||||||
|
|
||||||
// What a program is run with. Any pointer may be NULL (none / default).
|
// What a program is run with. Any pointer may be NULL (none / default).
|
||||||
typedef struct {
|
typedef struct {
|
||||||
const char *appDir; // App.Path; App.Data is its DATA folder (the files it ships)
|
const char *appDir; // App.Path; App.Data is its DATA folder (the files it ships)
|
||||||
|
|
@ -54,6 +57,7 @@ typedef struct {
|
||||||
void *doEventsCtx;
|
void *doEventsCtx;
|
||||||
bool suppressErrorDialog; // the host reports runtime errors itself
|
bool suppressErrorDialog; // the host reports runtime errors itself
|
||||||
bool standalone; // the .app's own program (basstub): see basRunHostCreate
|
bool standalone; // the .app's own program (basstub): see basRunHostCreate
|
||||||
|
bool endWithApp; // ended with the program it runs for when that is ended by force (the IDE's runs on a task of their own); standalone ones are
|
||||||
} BasRunSpecT;
|
} BasRunSpecT;
|
||||||
|
|
||||||
// A form's caption saved while " [break]" is appended to it.
|
// A form's caption saved while " [break]" is appended to it.
|
||||||
|
|
@ -71,10 +75,10 @@ bool basRunHostAlive(const BasFormRtT *rt);
|
||||||
|
|
||||||
// A VM running module (borrowed: it must outlive the runtime) and its
|
// A VM running module (borrowed: it must outlive the runtime) and its
|
||||||
// form runtime, set up from spec. A standalone program's runtime
|
// form runtime, set up from spec. A standalone program's runtime
|
||||||
// (spec->standalone, once it is made) owns the module, freed with it, and
|
// (spec->standalone, once it is made) owns the module, freed with it. It
|
||||||
// is ended as the program's own end would when the program is ended by
|
// and one made with spec->endWithApp are ended as the program's own end
|
||||||
// force (the shell's kill hook); the IDE and basdev end theirs
|
// would when the program they run for (ctx->currentAppId) is ended by
|
||||||
// themselves. Returns the runtime (rt->vm is the
|
// force (the shell's kill hook); basdev ends its runs itself. Returns the runtime (rt->vm is the
|
||||||
// VM), or NULL with *outError set when memory runs out. A module the VM
|
// VM), or NULL with *outError set when memory runs out. A module the VM
|
||||||
// refuses (too many globals) leaves rt->vm->module NULL; running it then
|
// refuses (too many globals) leaves rt->vm->module NULL; running it then
|
||||||
// fails with the reason in basVmGetError.
|
// fails with the reason in basVmGetError.
|
||||||
|
|
|
||||||
|
|
@ -61,9 +61,11 @@
|
||||||
// Running code: a program's own controls run in its VM with its rules (an
|
// Running code: a program's own controls run in its VM with its rules (an
|
||||||
// untrapped error ends the program). An installed control file has one VM
|
// untrapped error ends the program). An installed control file has one VM
|
||||||
// shared by every program that uses its controls: it has no DoEvents host
|
// shared by every program that uses its controls: it has no DoEvents host
|
||||||
// (its code never yields), a call from another task while it runs code is
|
// (its code never yields, so a call that runs too long is stopped and its
|
||||||
// refused (the event is dropped, a paint keeps the old picture; logged
|
// waits are refused: UC_RUN_LIMIT_SLICES), a call from another task while
|
||||||
// once), and an untrapped error is shown and the control carries on.
|
// it runs code is refused (the event is dropped, a paint keeps the old
|
||||||
|
// picture; logged once), and an untrapped error is shown and the control
|
||||||
|
// carries on.
|
||||||
//
|
//
|
||||||
// Lifetime: a widget destroyed while its code runs (a host handler that
|
// Lifetime: a widget destroyed while its code runs (a host handler that
|
||||||
// unloads the form) stays allocated, cut off from its widgets, until the
|
// unloads the form) stays allocated, cut off from its widgets, until the
|
||||||
|
|
@ -79,7 +81,6 @@
|
||||||
#include "dvxPlat.h"
|
#include "dvxPlat.h"
|
||||||
#include "thirdparty/stb_ds_wrap.h"
|
#include "thirdparty/stb_ds_wrap.h"
|
||||||
|
|
||||||
#include <dlfcn.h>
|
|
||||||
#include <inttypes.h>
|
#include <inttypes.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
|
@ -100,6 +101,7 @@
|
||||||
// The mouse event handlers' four arguments, a key handler's two.
|
// The mouse event handlers' four arguments, a key handler's two.
|
||||||
#define UC_MOUSE_ARGS 4
|
#define UC_MOUSE_ARGS 4
|
||||||
#define UC_KEY_ARGS 2
|
#define UC_KEY_ARGS 2
|
||||||
|
#define UC_EVENT_ARGS_MAX UC_MOUSE_ARGS
|
||||||
|
|
||||||
// WGT_METHOD_ON_MOUSE_EVENT gives a pressed or released button as its
|
// WGT_METHOD_ON_MOUSE_EVENT gives a pressed or released button as its
|
||||||
// ordinal (1 left, 2 right, 3 middle); BASIC handlers get VB's button
|
// ordinal (1 left, 2 right, 3 middle); BASIC handlers get VB's button
|
||||||
|
|
@ -130,6 +132,12 @@ static const int32_t sButtonBits[UC_MOUSE_ORDINALS] = { 0, MOUSE_LEFT, MOUSE_RIG
|
||||||
#define UC_PROGRAM_KEY_EXTRA 12
|
#define UC_PROGRAM_KEY_EXTRA 12
|
||||||
#define UC_TYPE_KEY_MAX (BAS_MAX_IDENT + UC_PROGRAM_KEY_EXTRA)
|
#define UC_TYPE_KEY_MAX (BAS_MAX_IDENT + UC_PROGRAM_KEY_EXTRA)
|
||||||
|
|
||||||
|
// An installed control file's code has no event pump, so nothing else runs
|
||||||
|
// while it does: a call into it (or its module code) that runs more than
|
||||||
|
// this many slices (about two seconds) is stopped with error 18, and its
|
||||||
|
// waits (SLEEP ...) are refused (basVmSetRunLimit).
|
||||||
|
#define UC_RUN_LIMIT_SLICES 40
|
||||||
|
|
||||||
// An installed control file: its module, and the runtime and VM its code
|
// An installed control file: its module, and the runtime and VM its code
|
||||||
// runs in (made with its first control). A designer's project controls
|
// runs in (made with its first control). A designer's project controls
|
||||||
// (basCtlLoadDesign) are one too.
|
// (basCtlLoadDesign) are one too.
|
||||||
|
|
@ -172,6 +180,14 @@ typedef struct {
|
||||||
bool retired; // unregistered (its program ended); freed with its last control
|
bool retired; // unregistered (its program ended); freed with its last control
|
||||||
} UcClassT;
|
} UcClassT;
|
||||||
|
|
||||||
|
// An event of a control whose program runs on another task, waiting for
|
||||||
|
// that program's own (ucDefer, basUcPump), its arguments copied.
|
||||||
|
typedef struct {
|
||||||
|
int32_t evt;
|
||||||
|
int32_t argc;
|
||||||
|
BasValueT args[UC_EVENT_ARGS_MAX];
|
||||||
|
} UcPendingT;
|
||||||
|
|
||||||
// One control (widget).
|
// One control (widget).
|
||||||
typedef struct {
|
typedef struct {
|
||||||
UcClassT *cls;
|
UcClassT *cls;
|
||||||
|
|
@ -192,6 +208,7 @@ typedef struct {
|
||||||
bool inPaint;
|
bool inPaint;
|
||||||
bool dead; // its widget is gone; freed when depth is 0
|
bool dead; // its widget is gone; freed when depth is 0
|
||||||
bool orphan; // its program ended first: no object, no code, only the widget left
|
bool orphan; // its program ended first: no object, no code, only the widget left
|
||||||
|
UcPendingT *pending; // stb_ds: events from other tasks, oldest first (basUcPump)
|
||||||
} UcInstT;
|
} UcInstT;
|
||||||
|
|
||||||
static UcClassT **sClasses = NULL; // stb_ds
|
static UcClassT **sClasses = NULL; // stb_ds
|
||||||
|
|
@ -199,20 +216,20 @@ static uint32_t sProgramKeys = 0; // serials of program-owned ty
|
||||||
static UcInstT **sInsts = NULL; // stb_ds
|
static UcInstT **sInsts = NULL; // stb_ds
|
||||||
static UcFileT **sFiles = NULL; // stb_ds
|
static UcFileT **sFiles = NULL; // stb_ds
|
||||||
static BasUcPropChangedFnT sPropChanged = NULL;
|
static BasUcPropChangedFnT sPropChanged = NULL;
|
||||||
static uint32_t (*sTaskIdFn)(void) = NULL;
|
|
||||||
static bool sTaskIdResolved = false;
|
|
||||||
|
|
||||||
// Function prototypes (alphabetical)
|
// Function prototypes (alphabetical)
|
||||||
int32_t basCtlLoadDesign(const BasFormRtT *rt, BasModuleT *module, const char *const *headers, int32_t count, const char *path);
|
int32_t basCtlLoadDesign(const BasFormRtT *rt, BasModuleT *module, const char *const *headers, int32_t count, const char *path);
|
||||||
bool basCtlLoadFile(const char *path);
|
bool basCtlLoadFile(const char *path);
|
||||||
bool basCtlUnloadDesign(const BasFormRtT *rt);
|
bool basCtlUnloadDesign(const BasFormRtT *rt);
|
||||||
bool basFormRtRegisterCtl(BasFormRtT *rt, const char *text, int32_t len);
|
bool basFormRtRegisterCtl(BasFormRtT *rt, const char *text, int32_t len);
|
||||||
|
bool basUcBusy(void);
|
||||||
bool basUcHeaderParse(const char *text, int32_t len, BasUcHeaderT *out);
|
bool basUcHeaderParse(const char *text, int32_t len, BasUcHeaderT *out);
|
||||||
bool basUcIconPath(const WgtIfaceT *iface, char *out, int32_t outSize);
|
bool basUcIconPath(const WgtIfaceT *iface, char *out, int32_t outSize);
|
||||||
const BasUcHeaderT *basUcIfaceHeader(const WgtIfaceT *iface);
|
const BasUcHeaderT *basUcIfaceHeader(const WgtIfaceT *iface);
|
||||||
bool basUcIfaceVisible(const BasFormRtT *rt, const WgtIfaceT *iface);
|
bool basUcIfaceVisible(const BasFormRtT *rt, const WgtIfaceT *iface);
|
||||||
bool basUcIsControlType(const char *typeName);
|
bool basUcIsControlType(const char *typeName);
|
||||||
const WgtIfaceT *basUcOwnIface(const BasFormRtT *rt, const char *typeName);
|
const WgtIfaceT *basUcOwnIface(const BasFormRtT *rt, const char *typeName);
|
||||||
|
void basUcPump(void);
|
||||||
BasControlT *basUcResolveSurface(const BasFormRtT *rt, void *ref);
|
BasControlT *basUcResolveSurface(const BasFormRtT *rt, void *ref);
|
||||||
const char *basUcResolveType(const BasFormRtT *rt, const char *typeName);
|
const char *basUcResolveType(const BasFormRtT *rt, const char *typeName);
|
||||||
void basUcRuntimeEnd(BasFormRtT *rt);
|
void basUcRuntimeEnd(BasFormRtT *rt);
|
||||||
|
|
@ -227,6 +244,7 @@ static bool ucCallEvent(UcInstT *inst, int32_t evt, const BasValueT *
|
||||||
static const BasClassInfoT *ucClassInfo(const UcClassT *cls);
|
static const BasClassInfoT *ucClassInfo(const UcClassT *cls);
|
||||||
static UcClassT *ucClassOf(const WgtIfaceT *iface);
|
static UcClassT *ucClassOf(const WgtIfaceT *iface);
|
||||||
static WidgetT *ucCreate(WidgetT *parent, const WgtIfaceT *iface);
|
static WidgetT *ucCreate(WidgetT *parent, const WgtIfaceT *iface);
|
||||||
|
static bool ucDefer(UcInstT *inst, int32_t evt, const BasValueT *args, int32_t argc);
|
||||||
static void ucDestroy(WidgetT *w);
|
static void ucDestroy(WidgetT *w);
|
||||||
static void ucDropObject(UcInstT *inst);
|
static void ucDropObject(UcInstT *inst);
|
||||||
static bool ucDynCall(WidgetT *w, int32_t methodIdx, const WgtValueT *args, int32_t argc, WgtValueT *ret);
|
static bool ucDynCall(WidgetT *w, int32_t methodIdx, const WgtValueT *args, int32_t argc, WgtValueT *ret);
|
||||||
|
|
@ -238,6 +256,7 @@ static void ucEventHost(void *ctx, const BasObjectT *obj, int32_t eve
|
||||||
static bool ucFileReady(UcFileT *file, WidgetT *parent);
|
static bool ucFileReady(UcFileT *file, WidgetT *parent);
|
||||||
static UcInstT *ucFindInst(const BasObjectT *obj);
|
static UcInstT *ucFindInst(const BasObjectT *obj);
|
||||||
static void ucFocus(WidgetT *w);
|
static void ucFocus(WidgetT *w);
|
||||||
|
static bool ucForeign(const UcInstT *inst);
|
||||||
static void ucFreeClass(UcClassT *cls);
|
static void ucFreeClass(UcClassT *cls);
|
||||||
static void ucFreeInst(UcInstT *inst);
|
static void ucFreeInst(UcInstT *inst);
|
||||||
static void ucHold(UcInstT *inst);
|
static void ucHold(UcInstT *inst);
|
||||||
|
|
@ -256,7 +275,9 @@ static const char *ucNameTaken(const BasFormRtT *rt, const UcFileT *file, co
|
||||||
static UcClassT *ucOwnClass(const BasFormRtT *rt, const char *name);
|
static UcClassT *ucOwnClass(const BasFormRtT *rt, const char *name);
|
||||||
static const BasFormRtT *ucOwner(const UcClassT *cls);
|
static const BasFormRtT *ucOwner(const UcClassT *cls);
|
||||||
static void ucPaintNow(UcInstT *inst);
|
static void ucPaintNow(UcInstT *inst);
|
||||||
|
static void ucPendingFree(UcPendingT *ev);
|
||||||
static void ucPoll(WidgetT *w, WindowT *win);
|
static void ucPoll(WidgetT *w, WindowT *win);
|
||||||
|
static void ucPollNow(UcInstT *inst);
|
||||||
static bool ucPrivate(const UcClassT *cls);
|
static bool ucPrivate(const UcClassT *cls);
|
||||||
static bool ucPrivateIface(const WgtIfaceT *iface);
|
static bool ucPrivateIface(const WgtIfaceT *iface);
|
||||||
static UcClassT *ucRegister(BasFormRtT *rt, UcFileT *file, const BasModuleT *module, const BasUcHeaderT *header);
|
static UcClassT *ucRegister(BasFormRtT *rt, UcFileT *file, const BasModuleT *module, const BasUcHeaderT *header);
|
||||||
|
|
@ -264,7 +285,6 @@ static bool ucRelease(UcInstT *inst);
|
||||||
static void ucRetireClass(UcClassT *cls);
|
static void ucRetireClass(UcClassT *cls);
|
||||||
static BasFormRtT *ucRt(const UcClassT *cls);
|
static BasFormRtT *ucRt(const UcClassT *cls);
|
||||||
static UcInstT *ucSurfaceInst(const BasFormRtT *rt, const void *ref);
|
static UcInstT *ucSurfaceInst(const BasFormRtT *rt, const void *ref);
|
||||||
static uint32_t ucTaskId(void);
|
|
||||||
static WgtValueT ucToWgt(UcInstT *inst, BasValueT v, uint8_t want);
|
static WgtValueT ucToWgt(UcInstT *inst, BasValueT v, uint8_t want);
|
||||||
static bool ucUserMode(const UcInstT *inst);
|
static bool ucUserMode(const UcInstT *inst);
|
||||||
static bool ucWheel(WidgetT *w, int32_t delta);
|
static bool ucWheel(WidgetT *w, int32_t delta);
|
||||||
|
|
@ -429,6 +449,19 @@ bool basFormRtRegisterCtl(BasFormRtT *rt, const char *text, int32_t len) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// A control has events from another task waiting for its program's turn
|
||||||
|
// (basFormRtBusy).
|
||||||
|
bool basUcBusy(void) {
|
||||||
|
for (int32_t i = 0; i < (int32_t)arrlen(sInsts); i++) {
|
||||||
|
if (arrlen(sInsts[i]->pending) > 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
bool basUcHeaderParse(const char *text, int32_t len, BasUcHeaderT *out) {
|
bool basUcHeaderParse(const char *text, int32_t len, BasUcHeaderT *out) {
|
||||||
char value[FRM_MAX_LINE_LEN];
|
char value[FRM_MAX_LINE_LEN];
|
||||||
|
|
||||||
|
|
@ -520,6 +553,46 @@ const WgtIfaceT *basUcOwnIface(const BasFormRtT *rt, const char *typeName) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// The program's turn on its own task (basFormRtUpdate): the events its
|
||||||
|
// controls got on other tasks run, oldest first, then their Resize and
|
||||||
|
// Paint. Every control taken is held, so one a handler destroys stays
|
||||||
|
// readable until all have run.
|
||||||
|
void basUcPump(void) {
|
||||||
|
UcInstT **todo = NULL;
|
||||||
|
|
||||||
|
for (int32_t i = 0; i < (int32_t)arrlen(sInsts); i++) {
|
||||||
|
const BasFormRtT *rt = ucRt(sInsts[i]->cls);
|
||||||
|
|
||||||
|
if (!sInsts[i]->dead && !sInsts[i]->orphan && rt && rt->ownTask == basFormRtTaskId()) {
|
||||||
|
ucHold(sInsts[i]);
|
||||||
|
arrput(todo, sInsts[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int32_t i = 0; i < (int32_t)arrlen(todo); i++) {
|
||||||
|
UcInstT *inst = todo[i];
|
||||||
|
|
||||||
|
while (arrlen(inst->pending) > 0 && !inst->dead) {
|
||||||
|
UcPendingT ev = inst->pending[0];
|
||||||
|
|
||||||
|
arrdel(inst->pending, 0);
|
||||||
|
ucCallEvent(inst, ev.evt, ev.args, ev.argc);
|
||||||
|
ucPendingFree(&ev);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!inst->dead) {
|
||||||
|
ucPollNow(inst);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int32_t i = 0; i < (int32_t)arrlen(todo); i++) {
|
||||||
|
ucRelease(todo[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
arrfree(todo);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
BasControlT *basUcResolveSurface(const BasFormRtT *rt, void *ref) {
|
BasControlT *basUcResolveSurface(const BasFormRtT *rt, void *ref) {
|
||||||
UcInstT *inst = ucSurfaceInst(rt, ref);
|
UcInstT *inst = ucSurfaceInst(rt, ref);
|
||||||
|
|
||||||
|
|
@ -835,7 +908,16 @@ static bool ucCallEvent(UcInstT *inst, int32_t evt, const BasValueT *args, int32
|
||||||
int32_t proc = ucClassInfo(inst->cls)->ucProcs[evt];
|
int32_t proc = ucClassInfo(inst->cls)->ucProcs[evt];
|
||||||
BasFormRtT *prev = NULL;
|
BasFormRtT *prev = NULL;
|
||||||
|
|
||||||
if (proc < 0 || inst->dead || !inst->obj || !ucEnter(inst, &prev)) {
|
if (proc < 0 || inst->dead || !inst->obj) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Its program's code runs only on its program's task
|
||||||
|
if (ucForeign(inst)) {
|
||||||
|
return ucDefer(inst, evt, args, argc);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ucEnter(inst, &prev)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -943,6 +1025,38 @@ static WidgetT *ucCreate(WidgetT *parent, const WgtIfaceT *iface) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// An event of a control whose program runs on another task waits for
|
||||||
|
// that program's turn (basUcPump); a newer MouseMove replaces one still
|
||||||
|
// waiting. True: it will run.
|
||||||
|
static bool ucDefer(UcInstT *inst, int32_t evt, const BasValueT *args, int32_t argc) {
|
||||||
|
UcPendingT *ev = NULL;
|
||||||
|
|
||||||
|
for (int32_t i = 0; evt == BAS_UC_EVT_MOUSEMOVE && i < (int32_t)arrlen(inst->pending); i++) {
|
||||||
|
if (inst->pending[i].evt == evt) {
|
||||||
|
ev = &inst->pending[i];
|
||||||
|
ucPendingFree(ev);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ev) {
|
||||||
|
UcPendingT fresh;
|
||||||
|
|
||||||
|
memset(&fresh, 0, sizeof(fresh));
|
||||||
|
arrput(inst->pending, fresh);
|
||||||
|
ev = &arrlast(inst->pending);
|
||||||
|
}
|
||||||
|
|
||||||
|
ev->evt = evt;
|
||||||
|
ev->argc = DVX_MIN(argc, UC_EVENT_ARGS_MAX);
|
||||||
|
|
||||||
|
for (int32_t i = 0; i < ev->argc; i++) {
|
||||||
|
ev->args[i] = basValCopy(args[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// The widget is being destroyed (its canvas already is): UserControl_
|
// The widget is being destroyed (its canvas already is): UserControl_
|
||||||
// Terminate, then the object goes. While code of it runs, the control
|
// Terminate, then the object goes. While code of it runs, the control
|
||||||
// stays allocated, cut off from the widgets, until the calls return. The
|
// stays allocated, cut off from the widgets, until the calls return. The
|
||||||
|
|
@ -1084,7 +1198,7 @@ static bool ucEnter(UcInstT *inst, BasFormRtT **outPrev) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (file) {
|
if (file) {
|
||||||
uint32_t task = ucTaskId();
|
uint32_t task = basFormRtTaskId();
|
||||||
|
|
||||||
if (file->depth > 0 && file->task != task) {
|
if (file->depth > 0 && file->task != task) {
|
||||||
if (!file->refusedLogged) {
|
if (!file->refusedLogged) {
|
||||||
|
|
@ -1230,6 +1344,8 @@ static bool ucFileReady(UcFileT *file, WidgetT *parent) {
|
||||||
}
|
}
|
||||||
|
|
||||||
basVmSetEventHost(file->rt->vm, ucEventHost, NULL);
|
basVmSetEventHost(file->rt->vm, ucEventHost, NULL);
|
||||||
|
basVmSetRunLimit(file->rt->vm, UC_RUN_LIMIT_SLICES);
|
||||||
|
file->rt->ownTask = BAS_RT_ANY_TASK;
|
||||||
|
|
||||||
// A designer's controls never run the module-level code: it is the
|
// A designer's controls never run the module-level code: it is the
|
||||||
// program's start
|
// program's start
|
||||||
|
|
@ -1278,6 +1394,16 @@ static void ucFocus(WidgetT *w) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// The control's program runs on another task: its code runs on that
|
||||||
|
// task alone (two tasks in one VM would interleave their calls). An
|
||||||
|
// installed file's runs on any (ucEnter refuses a second while it runs).
|
||||||
|
static bool ucForeign(const UcInstT *inst) {
|
||||||
|
const BasFormRtT *rt = ucRt(inst->cls);
|
||||||
|
|
||||||
|
return rt && rt->ownTask != BAS_RT_ANY_TASK && rt->ownTask != basFormRtTaskId();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// A class's memory (out of the registry, its controls gone).
|
// A class's memory (out of the registry, its controls gone).
|
||||||
static void ucFreeClass(UcClassT *cls) {
|
static void ucFreeClass(UcClassT *cls) {
|
||||||
for (int32_t i = 0; i < (int32_t)arrlen(cls->texts); i++) {
|
for (int32_t i = 0; i < (int32_t)arrlen(cls->texts); i++) {
|
||||||
|
|
@ -1319,6 +1445,11 @@ static void ucFreeInst(UcInstT *inst) {
|
||||||
basStringUnref(inst->hold);
|
basStringUnref(inst->hold);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (int32_t i = 0; i < (int32_t)arrlen(inst->pending); i++) {
|
||||||
|
ucPendingFree(&inst->pending[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
arrfree(inst->pending);
|
||||||
basFormRtDrawFree(&inst->surface);
|
basFormRtDrawFree(&inst->surface);
|
||||||
|
|
||||||
UcClassT *cls = inst->cls;
|
UcClassT *cls = inst->cls;
|
||||||
|
|
@ -1647,21 +1778,42 @@ static void ucPaintNow(UcInstT *inst) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Between frames: a size the layout gave the control makes a canvas of
|
// A waiting event's copied arguments go.
|
||||||
// that size (cleared to its BackColor), then Resize and Paint; a Refresh
|
static void ucPendingFree(UcPendingT *ev) {
|
||||||
// that came during Paint paints again.
|
for (int32_t i = 0; i < ev->argc; i++) {
|
||||||
|
basValRelease(&ev->args[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
ev->argc = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// The widget poll between frames (ucPollNow).
|
||||||
|
// cppcheck-suppress constParameterCallback ; a widget class method: WidgetClassT fixes the signature
|
||||||
static void ucPoll(WidgetT *w, WindowT *win) {
|
static void ucPoll(WidgetT *w, WindowT *win) {
|
||||||
UcInstT *inst = ucInstOf(w);
|
UcInstT *inst = ucInstOf(w);
|
||||||
|
|
||||||
(void)win;
|
(void)win;
|
||||||
|
|
||||||
if (!inst) {
|
// On another task than its program's, its program's turn does it
|
||||||
|
// (basUcPump)
|
||||||
|
if (!inst || ucForeign(inst)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ucHold(inst);
|
ucHold(inst);
|
||||||
|
ucPollNow(inst);
|
||||||
|
ucRelease(inst);
|
||||||
|
}
|
||||||
|
|
||||||
if (inst->sizeDirty && w->w > 0 && w->h > 0) {
|
|
||||||
|
// ucPoll's work (the caller holds inst): a new size makes a canvas of
|
||||||
|
// that size, then Resize and Paint; a Refresh that came during Paint
|
||||||
|
// paints again.
|
||||||
|
static void ucPollNow(UcInstT *inst) {
|
||||||
|
WidgetT *w = inst->self;
|
||||||
|
|
||||||
|
if (inst->sizeDirty && w && w->w > 0 && w->h > 0) {
|
||||||
inst->sizeDirty = false;
|
inst->sizeDirty = false;
|
||||||
inst->lastW = w->w;
|
inst->lastW = w->w;
|
||||||
inst->lastH = w->h;
|
inst->lastH = w->h;
|
||||||
|
|
@ -1674,8 +1826,6 @@ static void ucPoll(WidgetT *w, WindowT *win) {
|
||||||
if (inst->paintPending && !inst->dead) {
|
if (inst->paintPending && !inst->dead) {
|
||||||
ucPaintNow(inst);
|
ucPaintNow(inst);
|
||||||
}
|
}
|
||||||
|
|
||||||
ucRelease(inst);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1831,18 +1981,6 @@ static UcInstT *ucSurfaceInst(const BasFormRtT *rt, const void *ref) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// The running task (the task switcher's, found at run time; 0 without
|
|
||||||
// one).
|
|
||||||
static uint32_t ucTaskId(void) {
|
|
||||||
if (!sTaskIdResolved) {
|
|
||||||
sTaskIdResolved = true;
|
|
||||||
sTaskIdFn = (uint32_t (*)(void))dlsym(NULL, DVX_SYM("tsCurrentId"));
|
|
||||||
}
|
|
||||||
|
|
||||||
return sTaskIdFn ? sTaskIdFn() : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// A BASIC value as the widget value the host gets: its text held by the
|
// A BASIC value as the widget value the host gets: its text held by the
|
||||||
// control until the next value it gives.
|
// control until the next value it gives.
|
||||||
static WgtValueT ucToWgt(UcInstT *inst, BasValueT v, uint8_t want) {
|
static WgtValueT ucToWgt(UcInstT *inst, BasValueT v, uint8_t want) {
|
||||||
|
|
|
||||||
|
|
@ -967,8 +967,9 @@ const char *CtlTypeEventParams(const char *typeName, const char *eventName) {
|
||||||
|
|
||||||
|
|
||||||
// designer.bas: CTL_FLAG_CONTAINER (holds controls) and CTL_FLAG_DESIGNABLE
|
// designer.bas: CTL_FLAG_CONTAINER (holds controls) and CTL_FLAG_DESIGNABLE
|
||||||
// (can be created with CreateControl, so placed in a designer); -1 for an
|
// (can be created with CreateControl, so placed in a designer: every type
|
||||||
// unknown type.
|
// can, an Image or ImageButton with an empty picture); -1 for an unknown
|
||||||
|
// type.
|
||||||
int32_t CtlTypeFlags(const char *typeName) {
|
int32_t CtlTypeFlags(const char *typeName) {
|
||||||
const WgtIfaceT *iface = basCtlIface(typeName);
|
const WgtIfaceT *iface = basCtlIface(typeName);
|
||||||
|
|
||||||
|
|
@ -976,7 +977,7 @@ int32_t CtlTypeFlags(const char *typeName) {
|
||||||
return BAS_CTL_TYPE_UNKNOWN;
|
return BAS_CTL_TYPE_UNKNOWN;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (iface->isContainer ? BAS_CTL_FLAG_CONTAINER : 0) | ((iface->createSig != WGT_CREATE_PARENT_DATA) ? BAS_CTL_FLAG_DESIGNABLE : 0);
|
return (iface->isContainer ? BAS_CTL_FLAG_CONTAINER : 0) | BAS_CTL_FLAG_DESIGNABLE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -67,6 +67,8 @@
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <strings.h>
|
#include <strings.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <time.h>
|
||||||
|
|
||||||
#define SCREEN_SCALE_MAX 3 // largest whole-number window scale
|
#define SCREEN_SCALE_MAX 3 // largest whole-number window scale
|
||||||
#define SCREEN_PIXEL_SINGLE 1 // a framebuffer pixel one window pixel across / down (square)
|
#define SCREEN_PIXEL_SINGLE 1 // a framebuffer pixel one window pixel across / down (square)
|
||||||
|
|
@ -117,6 +119,13 @@
|
||||||
#define DRAW_ACCEL_ENABLED_ARG 1 // PrintAccel text [, enabled]
|
#define DRAW_ACCEL_ENABLED_ARG 1 // PrintAccel text [, enabled]
|
||||||
#define DRAW_ACCEL_MAX_ARGS 2
|
#define DRAW_ACCEL_MAX_ARGS 2
|
||||||
#define DRAW_PAINT_KEY_ARG 10 // PaintPicture ... , vbTransparentCopy, key colour
|
#define DRAW_PAINT_KEY_ARG 10 // PaintPicture ... , vbTransparentCopy, key colour
|
||||||
|
#define DRAW_NO_COLUMN (-1) // PaintPicture: a destination column with no source pixel
|
||||||
|
|
||||||
|
// PaintPicture's pictures kept decoded for the runtime (drawPictureLoad):
|
||||||
|
// at most this many bytes, and a file changed this recently is decoded
|
||||||
|
// again (FAT times are 2 seconds apart)
|
||||||
|
#define DRAW_PICS_KEEP_BYTES (1024 * 1024)
|
||||||
|
#define DRAW_PICS_TIME_SEC 2
|
||||||
|
|
||||||
// The scale units (BAS_SCALE_*) in twips: 15 to a pixel, as VB on a
|
// The scale units (BAS_SCALE_*) in twips: 15 to a pixel, as VB on a
|
||||||
// standard (96 DPI) display, whatever the real display is; characters are
|
// standard (96 DPI) display, whatever the real display is; characters are
|
||||||
|
|
@ -299,7 +308,9 @@ static bool drawIsSurface(const BasControlT *ctrl);
|
||||||
static bool drawMeasure(BasDrawT *d, const BitmapFontT *font, DrawMethodE method, const BasValueT *args, int32_t argc, BasValueT *result);
|
static bool drawMeasure(BasDrawT *d, const BitmapFontT *font, DrawMethodE method, const BasValueT *args, int32_t argc, BasValueT *result);
|
||||||
static void drawPaintKeyed(BasFormRtT *rt, BasDrawT *d, WidgetT *src, const uint8_t *pixels, int32_t pitch, uint32_t opcode, const BasValueT *args, int32_t argc, int32_t sx, int32_t sy, int32_t sw, int32_t sh, int32_t dx, int32_t dy);
|
static void drawPaintKeyed(BasFormRtT *rt, BasDrawT *d, WidgetT *src, const uint8_t *pixels, int32_t pitch, uint32_t opcode, const BasValueT *args, int32_t argc, int32_t sx, int32_t sy, int32_t sw, int32_t sh, int32_t dx, int32_t dy);
|
||||||
static void drawPaintPicture(BasFormRtT *rt, BasControlT *ctrl, BasDrawT *d, BasValueT *args, int32_t argc);
|
static void drawPaintPicture(BasFormRtT *rt, BasControlT *ctrl, BasDrawT *d, BasValueT *args, int32_t argc);
|
||||||
static bool drawPictureOf(BasFormRtT *rt, BasFormT *form, BasValueT v, WidgetT **src, uint8_t **pixels, int32_t *w, int32_t *h, int32_t *pitch, const BasGfxT **scale);
|
static void drawPaintRows(BasFormRtT *rt, BasDrawT *d, WidgetT *src, const uint8_t *pixels, int32_t pitch, int32_t srcW, int32_t srcH, uint32_t opcode, int32_t sx, int32_t sy, int32_t sw, int32_t sh, int32_t dx, int32_t dy, int32_t dw, int32_t dh);
|
||||||
|
static uint8_t *drawPictureLoad(BasFormRtT *rt, const char *path, const BasPictureT *pic, int32_t *w, int32_t *h, int32_t *pitch, bool *owned);
|
||||||
|
static bool drawPictureOf(BasFormRtT *rt, BasFormT *form, BasValueT v, WidgetT **src, uint8_t **pixels, int32_t *w, int32_t *h, int32_t *pitch, const BasGfxT **scale, bool *owned);
|
||||||
static int32_t drawPixelSpan(const BasGfxT *g, double length, bool across);
|
static int32_t drawPixelSpan(const BasGfxT *g, double length, bool across);
|
||||||
static void drawPrint(BasDrawT *d, const BitmapFontT *font, BasValueT value, int32_t mode);
|
static void drawPrint(BasDrawT *d, const BitmapFontT *font, BasValueT value, int32_t mode);
|
||||||
static uint32_t drawRasterOp(uint32_t rop, uint32_t pattern, uint32_t source, uint32_t dest);
|
static uint32_t drawRasterOp(uint32_t rop, uint32_t pattern, uint32_t source, uint32_t dest);
|
||||||
|
|
@ -520,6 +531,16 @@ BasDrawT *basFormRtDrawNew(AppContextT *ctx, const BasGfxSurfaceT *surf, double
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void basFormRtDrawPicsFree(BasFormRtT *rt) {
|
||||||
|
for (int32_t i = 0; i < (int32_t)arrlen(rt->drawPics); i++) {
|
||||||
|
dvxFreeImage(rt->drawPics[i].pixels);
|
||||||
|
}
|
||||||
|
|
||||||
|
arrfree(rt->drawPics);
|
||||||
|
rt->drawPicsBytes = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Columns and lines count charW x charH pixel cells from the left edge,
|
// Columns and lines count charW x charH pixel cells from the left edge,
|
||||||
// CurrentX / CurrentY kept in the scale; text moves CurrentX by the width
|
// CurrentX / CurrentY kept in the scale; text moves CurrentX by the width
|
||||||
// emit drew it at.
|
// emit drew it at.
|
||||||
|
|
@ -530,24 +551,27 @@ uint8_t *basFormRtDrawPictureRgb(BasFormRtT *rt, BasFormT *form, BasValueT pic,
|
||||||
uint8_t *pixels = NULL;
|
uint8_t *pixels = NULL;
|
||||||
WidgetT *src = NULL;
|
WidgetT *src = NULL;
|
||||||
int32_t pitch = 0;
|
int32_t pitch = 0;
|
||||||
int32_t bpp = rt->ctx->display.format.bytesPerPixel;
|
bool owned = false;
|
||||||
|
DisplayT canvas;
|
||||||
|
|
||||||
if (!drawPictureOf(rt, form, pic, &src, &pixels, w, h, &pitch, scale)) {
|
if (!drawPictureOf(rt, form, pic, &src, &pixels, w, h, &pitch, scale, &owned)) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t *rgb = (uint8_t *)malloc((size_t)*w * (size_t)*h * DRAW_RGB_BYTES);
|
// A PictureBox's (or form's) pixels are its canvas's, in the display's
|
||||||
|
// format either way
|
||||||
|
const uint8_t *buf = pixels;
|
||||||
|
|
||||||
for (int32_t y = 0; rgb && y < *h; y++) {
|
if (!buf && wgtCanvasGetDisplay(src, &canvas)) {
|
||||||
for (int32_t x = 0; x < *w; x++) {
|
buf = canvas.backBuf;
|
||||||
uint8_t *p = rgb + ((size_t)y * (size_t)*w + (size_t)x) * DRAW_RGB_BYTES;
|
pitch = canvas.pitch;
|
||||||
uint32_t pixel = pixels ? readPixel(pixels + y * pitch + x * bpp, bpp) : wgtCanvasGetPixel(src, x, y);
|
|
||||||
|
|
||||||
unpackColor(&rt->ctx->display, pixel, &p[0], &p[1], &p[2]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint8_t *rgb = buf ? dvxBufferToRgb(&rt->ctx->display, buf, *w, *h, pitch) : NULL;
|
||||||
|
|
||||||
|
if (owned) {
|
||||||
dvxFreeImage(pixels);
|
dvxFreeImage(pixels);
|
||||||
|
}
|
||||||
|
|
||||||
if (!rgb) {
|
if (!rgb) {
|
||||||
basFormRtRuntimeError(rt, "Out of memory", "There is not enough memory for the picture.");
|
basFormRtRuntimeError(rt, "Out of memory", "There is not enough memory for the picture.");
|
||||||
|
|
@ -1117,21 +1141,24 @@ static void drawPaintKeyed(BasFormRtT *rt, BasDrawT *d, WidgetT *src, const uint
|
||||||
// destination (nearest pixel), each pixel combined with the one there and
|
// destination (nearest pixel), each pixel combined with the one there and
|
||||||
// the FillColor pattern by the raster operation opcode (vbSrcCopy when
|
// the FillColor pattern by the raster operation opcode (vbSrcCopy when
|
||||||
// left out; vbSrcAnd, vbSrcPaint, vbSrcInvert ... -- any of Windows' 256).
|
// left out; vbSrcAnd, vbSrcPaint, vbSrcInvert ... -- any of Windows' 256).
|
||||||
|
// A plain copy at the source's size is one rectangle copy; otherwise each
|
||||||
|
// destination column's source column is worked out once and a row is
|
||||||
|
// built and copied whole, the canvas buffers read directly.
|
||||||
static void drawPaintPicture(BasFormRtT *rt, BasControlT *ctrl, BasDrawT *d, BasValueT *args, int32_t argc) {
|
static void drawPaintPicture(BasFormRtT *rt, BasControlT *ctrl, BasDrawT *d, BasValueT *args, int32_t argc) {
|
||||||
uint8_t *pixels = NULL;
|
uint8_t *pixels = NULL;
|
||||||
WidgetT *src = NULL;
|
WidgetT *src = NULL;
|
||||||
const BasGfxT *srcScale = NULL;
|
const BasGfxT *srcScale = NULL;
|
||||||
|
bool owned = false;
|
||||||
int32_t srcW = 0;
|
int32_t srcW = 0;
|
||||||
int32_t srcH = 0;
|
int32_t srcH = 0;
|
||||||
int32_t pitch = 0;
|
int32_t pitch = 0;
|
||||||
int32_t bpp = rt->ctx->display.format.bytesPerPixel;
|
|
||||||
|
|
||||||
if (argc < DRAW_PAINT_MIN_ARGS) {
|
if (argc < DRAW_PAINT_MIN_ARGS) {
|
||||||
basFormRtRuntimeError(rt, "PaintPicture needs a picture, x, and y", "Usage: PaintPicture picture, x, y [, width, height [, srcX, srcY, srcWidth, srcHeight [, opcode]]]");
|
basFormRtRuntimeError(rt, "PaintPicture needs a picture, x, and y", "Usage: PaintPicture picture, x, y [, width, height [, srcX, srcY, srcWidth, srcHeight [, opcode]]]");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!drawPictureOf(rt, ctrl->form, args[0], &src, &pixels, &srcW, &srcH, &pitch, &srcScale)) {
|
if (!drawPictureOf(rt, ctrl->form, args[0], &src, &pixels, &srcW, &srcH, &pitch, &srcScale, &owned)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1162,62 +1189,195 @@ static void drawPaintPicture(BasFormRtT *rt, BasControlT *ctrl, BasDrawT *d, Bas
|
||||||
// DVX's copies: greyed, or colour-keyed (at the source's size)
|
// DVX's copies: greyed, or colour-keyed (at the source's size)
|
||||||
if (opcode == BAS_ROP_GRAYCOPY || opcode == BAS_ROP_TRANSPCOPY) {
|
if (opcode == BAS_ROP_GRAYCOPY || opcode == BAS_ROP_TRANSPCOPY) {
|
||||||
drawPaintKeyed(rt, d, src, pixels, pitch, opcode, args, argc, sx, sy, sw, sh, dx, dy);
|
drawPaintKeyed(rt, d, src, pixels, pitch, opcode, args, argc, sx, sy, sw, sh, dx, dy);
|
||||||
|
} else {
|
||||||
|
drawPaintRows(rt, d, src, pixels, pitch, srcW, srcH, opcode, sx, sy, sw, sh, dx, dy, dw, dh);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (owned) {
|
||||||
dvxFreeImage(pixels);
|
dvxFreeImage(pixels);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// drawPaintPicture's raster-op copy onto the canvas, its source the image
|
||||||
|
// pixels or else the PictureBox src's canvas.
|
||||||
|
static void drawPaintRows(BasFormRtT *rt, BasDrawT *d, WidgetT *src, const uint8_t *pixels, int32_t pitch, int32_t srcW, int32_t srcH, uint32_t opcode, int32_t sx, int32_t sy, int32_t sw, int32_t sh, int32_t dx, int32_t dy, int32_t dw, int32_t dh) {
|
||||||
|
const BlitOpsT *ops = &rt->ctx->blitOps;
|
||||||
|
int32_t bpp = ops->bytesPerPixel;
|
||||||
|
DisplayT dst;
|
||||||
|
DisplayT srcDisp;
|
||||||
|
|
||||||
|
if (!pixels) {
|
||||||
|
if (!src || !wgtCanvasGetDisplay(src, &srcDisp)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
pixels = srcDisp.backBuf;
|
||||||
|
pitch = srcDisp.pitch;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sw <= 0 || sh <= 0 || dw <= 0 || dh <= 0 || !wgtCanvasGetDisplay(d->canvas, &dst)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t rop = (opcode >> BAS_ROP_TABLE_SHIFT) & BAS_ROP_TABLE_MASK;
|
uint32_t rop = (opcode >> BAS_ROP_TABLE_SHIFT) & BAS_ROP_TABLE_MASK;
|
||||||
bool copy = rop == (BAS_ROP_SRCCOPY >> BAS_ROP_TABLE_SHIFT);
|
bool copy = rop == (BAS_ROP_SRCCOPY >> BAS_ROP_TABLE_SHIFT);
|
||||||
|
|
||||||
|
// A plain copy at the source's size: one rectangle copy (rectCopy clips
|
||||||
|
// to the canvas), the source rectangle kept inside the source
|
||||||
|
if (copy && dw == sw && dh == sh) {
|
||||||
|
int32_t cutX = DVX_MAX(0, -sx);
|
||||||
|
int32_t cutY = DVX_MAX(0, -sy);
|
||||||
|
int32_t w = DVX_MIN(sw - cutX, srcW - (sx + cutX));
|
||||||
|
int32_t h = DVX_MIN(sh - cutY, srcH - (sy + cutY));
|
||||||
|
|
||||||
|
if (w > 0 && h > 0) {
|
||||||
|
rectCopy(&dst, ops, dx + cutX, dy + cutY, pixels, pitch, sx + cutX, sy + cutY, w, h);
|
||||||
|
wgtInvalidatePaint(d->canvas);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
uint32_t pattern = surfToPixel(d, d->gfx.fillColor);
|
uint32_t pattern = surfToPixel(d, d->gfx.fillColor);
|
||||||
uint32_t mask = (bpp >= (int32_t)sizeof(uint32_t)) ? UINT32_MAX : ((1u << (bpp * DRAW_BITS_PER_BYTE)) - 1);
|
uint32_t mask = (bpp >= (int32_t)sizeof(uint32_t)) ? UINT32_MAX : ((1u << (bpp * DRAW_BITS_PER_BYTE)) - 1);
|
||||||
|
int32_t *cols = (int32_t *)malloc((size_t)dw * sizeof(int32_t));
|
||||||
|
uint8_t *row = (uint8_t *)malloc((size_t)dw * (size_t)bpp);
|
||||||
|
|
||||||
for (int32_t y = 0; y < dh && sw > 0 && sh > 0; y++) {
|
if (!cols || !row) {
|
||||||
int32_t py = dy + y;
|
free(cols);
|
||||||
int32_t ry = sy + (int32_t)((int64_t)y * sh / dh);
|
free(row);
|
||||||
|
return;
|
||||||
if (py < 0 || py >= d->gfx.surf.height || ry < 0 || ry >= srcH) {
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int32_t x = 0; x < dw; x++) {
|
for (int32_t x = 0; x < dw; x++) {
|
||||||
int32_t px = dx + x;
|
|
||||||
int32_t rx = sx + (int32_t)((int64_t)x * sw / dw);
|
int32_t rx = sx + (int32_t)((int64_t)x * sw / dw);
|
||||||
|
|
||||||
if (px < 0 || px >= d->gfx.surf.width || rx < 0 || rx >= srcW) {
|
cols[x] = (rx >= 0 && rx < srcW) ? rx : DRAW_NO_COLUMN;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int32_t y = 0; y < dh; y++) {
|
||||||
|
int32_t py = dy + y;
|
||||||
|
int32_t ry = sy + (int32_t)((int64_t)y * sh / dh);
|
||||||
|
|
||||||
|
if (py < 0 || py >= dst.height || ry < 0 || ry >= srcH) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t pixel = pixels ? readPixel(pixels + ry * pitch + rx * bpp, bpp) : wgtCanvasGetPixel(src, rx, ry);
|
const uint8_t *srcRow = pixels + (size_t)ry * (size_t)pitch;
|
||||||
|
const uint8_t *dstRow = dst.backBuf + (size_t)py * (size_t)dst.pitch;
|
||||||
|
|
||||||
if (!copy) {
|
for (int32_t x = 0; x < dw; x++) {
|
||||||
pixel = drawRasterOp(rop, pattern, pixel, wgtCanvasGetPixel(d->canvas, px, py)) & mask;
|
int32_t px = dx + x;
|
||||||
|
bool inside = px >= 0 && px < dst.width;
|
||||||
|
uint32_t under = (inside && (!copy || cols[x] == DRAW_NO_COLUMN)) ? readPixel(dstRow + px * bpp, bpp) : 0;
|
||||||
|
uint32_t pixel = under;
|
||||||
|
|
||||||
|
if (cols[x] != DRAW_NO_COLUMN) {
|
||||||
|
pixel = readPixel(srcRow + cols[x] * bpp, bpp);
|
||||||
|
pixel = copy ? pixel : drawRasterOp(rop, pattern, pixel, under) & mask;
|
||||||
}
|
}
|
||||||
|
|
||||||
surfSpan(d, px, px, py, pixel);
|
memcpy(row + x * bpp, &pixel, (size_t)bpp);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dvxFreeImage(pixels);
|
rectCopy(&dst, ops, dx, py, row, dw * bpp, 0, 0, dw, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
free(cols);
|
||||||
|
free(row);
|
||||||
|
wgtInvalidatePaint(d->canvas);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// The pixels of a picture file (path) or of a LoadPictureData picture
|
||||||
|
// (pic) in the display format, as dvxLoadImage gives them: those kept
|
||||||
|
// from an earlier call while the file is unchanged (its size and time,
|
||||||
|
// and not changed in the last DRAW_PICS_TIME_SEC; a picture's bytes never
|
||||||
|
// change) and the display's pixel size the same, else decoded and, while
|
||||||
|
// there is room (DRAW_PICS_KEEP_BYTES for the runtime), kept: a picture
|
||||||
|
// drawn every frame is decoded once. *owned: the caller frees them (not
|
||||||
|
// kept).
|
||||||
|
static uint8_t *drawPictureLoad(BasFormRtT *rt, const char *path, const BasPictureT *pic, int32_t *w, int32_t *h, int32_t *pitch, bool *owned) {
|
||||||
|
struct stat st;
|
||||||
|
int32_t bpp = rt->ctx->display.format.bytesPerPixel;
|
||||||
|
bool stable = true;
|
||||||
|
|
||||||
|
memset(&st, 0, sizeof(st));
|
||||||
|
|
||||||
|
if (!pic) {
|
||||||
|
stable = stat(path, &st) == 0 && (int64_t)time(NULL) - (int64_t)st.st_mtime > DRAW_PICS_TIME_SEC;
|
||||||
|
}
|
||||||
|
|
||||||
|
*owned = true;
|
||||||
|
|
||||||
|
for (int32_t i = 0; i < (int32_t)arrlen(rt->drawPics); i++) {
|
||||||
|
BasDrawPicT *p = &rt->drawPics[i];
|
||||||
|
|
||||||
|
if (p->picture != pic || (!pic && !platformPathSame(p->path, path))) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stable && p->size == (int64_t)st.st_size && p->time == (int64_t)st.st_mtime && p->bpp == bpp) {
|
||||||
|
*w = p->w;
|
||||||
|
*h = p->h;
|
||||||
|
*pitch = p->pitch;
|
||||||
|
*owned = false;
|
||||||
|
return p->pixels;
|
||||||
|
}
|
||||||
|
|
||||||
|
rt->drawPicsBytes -= p->pitch * p->h;
|
||||||
|
dvxFreeImage(p->pixels);
|
||||||
|
arrdel(rt->drawPics, i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t *pixels = pic ? dvxLoadImageFromMemory(rt->ctx, pic->data, pic->dataLen, w, h, pitch) : dvxLoadImage(rt->ctx, path, w, h, pitch);
|
||||||
|
int32_t bytes = pixels ? *pitch * *h : 0;
|
||||||
|
|
||||||
|
if (!pixels || !stable || rt->drawPicsBytes + bytes > DRAW_PICS_KEEP_BYTES) {
|
||||||
|
return pixels;
|
||||||
|
}
|
||||||
|
|
||||||
|
BasDrawPicT kept;
|
||||||
|
|
||||||
|
memset(&kept, 0, sizeof(kept));
|
||||||
|
kept.pixels = pixels;
|
||||||
|
kept.picture = pic;
|
||||||
|
kept.size = (int64_t)st.st_size;
|
||||||
|
kept.time = (int64_t)st.st_mtime;
|
||||||
|
kept.w = *w;
|
||||||
|
kept.h = *h;
|
||||||
|
kept.pitch = *pitch;
|
||||||
|
kept.bpp = bpp;
|
||||||
|
snprintf(kept.path, sizeof(kept.path), "%s", pic ? "" : path);
|
||||||
|
arrput(rt->drawPics, kept);
|
||||||
|
rt->drawPicsBytes += bytes;
|
||||||
|
*owned = false;
|
||||||
|
return pixels;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// PaintPicture's picture: a PictureBox (a Control variable, or its name,
|
// PaintPicture's picture: a PictureBox (a Control variable, or its name,
|
||||||
// looked up from form first), else an image file (named, or a LOADPICTURE
|
// looked up from form first), else an image file (named, or a LOADPICTURE
|
||||||
// Picture object) loaded into *pixels in the display format (free with
|
// Picture object) or a LOADPICTUREDATA Picture object as *pixels in the
|
||||||
// dvxFreeImage). *scale is the PictureBox's scale (NULL: pixels). False,
|
// display format (drawPictureLoad; *owned: free them with dvxFreeImage).
|
||||||
// a runtime error raised, when there is none.
|
// *scale is the PictureBox's scale (NULL: pixels). False, a runtime error
|
||||||
static bool drawPictureOf(BasFormRtT *rt, BasFormT *form, BasValueT v, WidgetT **src, uint8_t **pixels, int32_t *w, int32_t *h, int32_t *pitch, const BasGfxT **scale) {
|
// raised, when there is none.
|
||||||
|
static bool drawPictureOf(BasFormRtT *rt, BasFormT *form, BasValueT v, WidgetT **src, uint8_t **pixels, int32_t *w, int32_t *h, int32_t *pitch, const BasGfxT **scale, bool *owned) {
|
||||||
|
const BasPictureT *bytes = basFormRtPictureData(rt, v);
|
||||||
const char *path = basFormRtPicturePath(rt, v);
|
const char *path = basFormRtPicturePath(rt, v);
|
||||||
BasStringT *name = path ? basStringNew(path, (int32_t)strlen(path)) : (v.type == BAS_TYPE_OBJECT ? NULL : basValFormatString(v));
|
BasStringT *name = path ? basStringNew(path, (int32_t)strlen(path)) : (v.type == BAS_TYPE_OBJECT ? NULL : basValFormatString(v));
|
||||||
void *ref = name ? basFormRtFindCtrl(rt, form, name->data) : v.objVal;
|
void *ref = name ? basFormRtFindCtrl(rt, form, name->data) : (bytes ? NULL : v.objVal);
|
||||||
BasControlT *pic = basFormRtResolveCtrl(rt, ref);
|
BasControlT *pic = basFormRtResolveCtrl(rt, ref);
|
||||||
|
|
||||||
*pixels = NULL;
|
*pixels = NULL;
|
||||||
|
*owned = false;
|
||||||
*src = pic && drawIsSurface(pic) && (!pic->form || pic != &pic->form->formCtrl) ? pic->widget : NULL;
|
*src = pic && drawIsSurface(pic) && (!pic->form || pic != &pic->form->formCtrl) ? pic->widget : NULL;
|
||||||
*scale = *src && pic->draw ? &pic->draw->gfx : NULL;
|
*scale = *src && pic->draw ? &pic->draw->gfx : NULL;
|
||||||
|
|
||||||
if (!*src && name) {
|
if (!*src && (name || bytes)) {
|
||||||
*pixels = dvxLoadImage(rt->ctx, name->data, w, h, pitch);
|
*pixels = drawPictureLoad(rt, name ? name->data : NULL, bytes, w, h, pitch, owned);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (name) {
|
if (name) {
|
||||||
|
|
@ -1225,7 +1385,7 @@ static bool drawPictureOf(BasFormRtT *rt, BasFormT *form, BasValueT v, WidgetT *
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!*pixels && (!*src || !canvasSize(*src, w, h))) {
|
if (!*pixels && (!*src || !canvasSize(*src, w, h))) {
|
||||||
basFormRtRuntimeError(rt, "PaintPicture has no picture", "The picture must be a PictureBox (its name, or a Control variable holding it), a BMP, PNG, JPEG, or GIF image file, a program, widget or library file (its icon), or a picture from LoadPicture.");
|
basFormRtRuntimeError(rt, "PaintPicture has no picture", "The picture must be a PictureBox (its name, or a Control variable holding it), a BMP, PNG, JPEG, or GIF image file, a program, widget or library file (its icon), or a picture from LoadPicture or LoadPictureData.");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -300,6 +300,7 @@ static bool sApiTried;
|
||||||
void basFormRtPrinterEnd(BasFormRtT *rt);
|
void basFormRtPrinterEnd(BasFormRtT *rt);
|
||||||
BasValueT basFormRtPrinterGetProp(BasFormRtT *rt, const char *name);
|
BasValueT basFormRtPrinterGetProp(BasFormRtT *rt, const char *name);
|
||||||
bool basFormRtPrinterIs(const void *ref);
|
bool basFormRtPrinterIs(const void *ref);
|
||||||
|
void basFormRtPrinterKill(BasFormRtT *rt);
|
||||||
BasValueT basFormRtPrinterMethod(BasFormRtT *rt, const char *name, BasValueT *args, int32_t argc);
|
BasValueT basFormRtPrinterMethod(BasFormRtT *rt, const char *name, BasValueT *args, int32_t argc);
|
||||||
void *basFormRtPrinterRef(void);
|
void *basFormRtPrinterRef(void);
|
||||||
void basFormRtPrinterSetProp(BasFormRtT *rt, const char *name, BasValueT value);
|
void basFormRtPrinterSetProp(BasFormRtT *rt, const char *name, BasValueT value);
|
||||||
|
|
@ -309,6 +310,7 @@ static void prEndDoc(BasFormRtT *rt, BasPrinterT *p);
|
||||||
static int32_t prFind(const char *const *names, int32_t count, const char *name);
|
static int32_t prFind(const char *const *names, int32_t count, const char *name);
|
||||||
static int32_t prFont(BasPrinterT *p);
|
static int32_t prFont(BasPrinterT *p);
|
||||||
static void prFontsClose(BasPrinterT *p);
|
static void prFontsClose(BasPrinterT *p);
|
||||||
|
static void prFree(BasFormRtT *rt);
|
||||||
static BasPrinterT *prGet(BasFormRtT *rt);
|
static BasPrinterT *prGet(BasFormRtT *rt);
|
||||||
static void prHome(BasPrinterT *p);
|
static void prHome(BasPrinterT *p);
|
||||||
static void prKillDoc(BasPrinterT *p);
|
static void prKillDoc(BasPrinterT *p);
|
||||||
|
|
@ -323,7 +325,7 @@ static void prPicture(BasFormRtT *rt, BasPrinterT *p, BasValueT *args, in
|
||||||
static void prPictureBand(const PrOpT *op, const PrBandT *b);
|
static void prPictureBand(const PrOpT *op, const PrBandT *b);
|
||||||
static bool prPrintPage(BasFormRtT *rt, BasPrinterT *p);
|
static bool prPrintPage(BasFormRtT *rt, BasPrinterT *p);
|
||||||
static void prShape(BasFormRtT *rt, BasPrinterT *p, PrOpKindE kind, BasValueT *args, int32_t argc);
|
static void prShape(BasFormRtT *rt, BasPrinterT *p, PrOpKindE kind, BasValueT *args, int32_t argc);
|
||||||
static void prShapeAgain(const BasPrinterT *p, const PrOpT *op, const BasGfxSurfaceT *surf);
|
static void prShapeAgain(const BasPrinterT *p, const PrOpT *op, const BasGfxSurfaceT *surf, int32_t bandY0, int32_t bandY1);
|
||||||
static int32_t prShapeDo(BasGfxT *g, PrOpKindE kind, const BasValueT *args);
|
static int32_t prShapeDo(BasGfxT *g, PrOpKindE kind, const BasValueT *args);
|
||||||
static int32_t prText(void *ctx, const char *text, int32_t len, int32_t x, int32_t y);
|
static int32_t prText(void *ctx, const char *text, int32_t len, int32_t x, int32_t y);
|
||||||
static double prTwipsX(const BasPrinterT *p);
|
static double prTwipsX(const BasPrinterT *p);
|
||||||
|
|
@ -347,9 +349,7 @@ void basFormRtPrinterEnd(BasFormRtT *rt) {
|
||||||
}
|
}
|
||||||
|
|
||||||
prEndDoc(rt, p);
|
prEndDoc(rt, p);
|
||||||
basFormRtDrawDelete(p->draw);
|
prFree(rt);
|
||||||
free(p);
|
|
||||||
rt->printer = NULL;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -426,6 +426,18 @@ bool basFormRtPrinterIs(const void *ref) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void basFormRtPrinterKill(BasFormRtT *rt) {
|
||||||
|
BasPrinterT *p = rt->printer;
|
||||||
|
|
||||||
|
if (!p) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
prKillDoc(p);
|
||||||
|
prFree(rt);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
BasValueT basFormRtPrinterMethod(BasFormRtT *rt, const char *name, BasValueT *args, int32_t argc) {
|
BasValueT basFormRtPrinterMethod(BasFormRtT *rt, const char *name, BasValueT *args, int32_t argc) {
|
||||||
BasValueT result = basValInteger(0);
|
BasValueT result = basValInteger(0);
|
||||||
BasPrinterT *p = prGet(rt);
|
BasPrinterT *p = prGet(rt);
|
||||||
|
|
@ -742,6 +754,14 @@ static void prFontsClose(BasPrinterT *p) {
|
||||||
|
|
||||||
// The runtime's Printer, made on first use; NULL (a runtime error raised)
|
// The runtime's Printer, made on first use; NULL (a runtime error raised)
|
||||||
// when printing is not installed.
|
// when printing is not installed.
|
||||||
|
// The program's Printer state gone (its document printed or abandoned).
|
||||||
|
static void prFree(BasFormRtT *rt) {
|
||||||
|
basFormRtDrawDelete(rt->printer->draw);
|
||||||
|
free(rt->printer);
|
||||||
|
rt->printer = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static BasPrinterT *prGet(BasFormRtT *rt) {
|
static BasPrinterT *prGet(BasFormRtT *rt) {
|
||||||
const PrApiT *api = prApi();
|
const PrApiT *api = prApi();
|
||||||
|
|
||||||
|
|
@ -916,7 +936,7 @@ static bool prPageBand(void *ctx, uint8_t *rgb, int32_t width, int32_t y0, int32
|
||||||
} else if (op->kind == PrOpPictureE) {
|
} else if (op->kind == PrOpPictureE) {
|
||||||
prPictureBand(op, &b);
|
prPictureBand(op, &b);
|
||||||
} else {
|
} else {
|
||||||
prShapeAgain(p, op, &surf);
|
prShapeAgain(p, op, &surf, y0, last);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1022,13 +1042,10 @@ static void prPictureBand(const PrOpT *op, const PrBandT *b) {
|
||||||
|
|
||||||
for (int32_t y = top; y <= bottom; y++) {
|
for (int32_t y = top; y <= bottom; y++) {
|
||||||
int32_t ry = op->u.picture.sy + (int32_t)((int64_t)(y - op->u.picture.dy) * op->u.picture.sh / op->u.picture.dh);
|
int32_t ry = op->u.picture.sy + (int32_t)((int64_t)(y - op->u.picture.dy) * op->u.picture.sh / op->u.picture.dh);
|
||||||
uint8_t *dst = b->rgb + (int64_t)(y - b->y0) * b->width * PR_RGB_BYTES;
|
uint8_t *dst = b->rgb + ((int64_t)(y - b->y0) * b->width + x0) * PR_RGB_BYTES;
|
||||||
|
const uint8_t *src = op->u.picture.rgb + ((int64_t)ry * op->u.picture.w + op->u.picture.sx) * PR_RGB_BYTES;
|
||||||
|
|
||||||
for (int32_t x = x0; x < x1; x++) {
|
drawScaleRow(dst, src, PR_RGB_BYTES, x0 - op->u.picture.dx, x1 - x0, op->u.picture.dw, op->u.picture.sw, false, 0);
|
||||||
int32_t rx = op->u.picture.sx + (int32_t)((int64_t)(x - op->u.picture.dx) * op->u.picture.sw / op->u.picture.dw);
|
|
||||||
|
|
||||||
memcpy(dst + (int64_t)x * PR_RGB_BYTES, op->u.picture.rgb + ((int64_t)ry * op->u.picture.w + rx) * PR_RGB_BYTES, PR_RGB_BYTES);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1114,14 +1131,18 @@ static void prShape(BasFormRtT *rt, BasPrinterT *p, PrOpKindE kind, BasValueT *a
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// A kept shape done again on surf, in the drawing state it was done in.
|
// A kept shape done again on surf, in the drawing state it was done in,
|
||||||
static void prShapeAgain(const BasPrinterT *p, const PrOpT *op, const BasGfxSurfaceT *surf) {
|
// only its rows bandY0..bandY1 (a band's: the fills and pen dots of the
|
||||||
|
// rest are not worked out at all).
|
||||||
|
static void prShapeAgain(const BasPrinterT *p, const PrOpT *op, const BasGfxSurfaceT *surf, int32_t bandY0, int32_t bandY1) {
|
||||||
BasGfxT g = p->states[op->u.shape.state];
|
BasGfxT g = p->states[op->u.shape.state];
|
||||||
BasValueT args[BAS_GFX_CIRCLE_ARGS];
|
BasValueT args[BAS_GFX_CIRCLE_ARGS];
|
||||||
|
|
||||||
g.surf = *surf;
|
g.surf = *surf;
|
||||||
g.lastX = op->u.shape.lastX;
|
g.lastX = op->u.shape.lastX;
|
||||||
g.lastY = op->u.shape.lastY;
|
g.lastY = op->u.shape.lastY;
|
||||||
|
g.bandY0 = bandY0;
|
||||||
|
g.bandY1 = bandY1;
|
||||||
|
|
||||||
for (int32_t a = 0; a < op->u.shape.argc; a++) {
|
for (int32_t a = 0; a < op->u.shape.argc; a++) {
|
||||||
args[a] = basValDouble(op->u.shape.args[a]);
|
args[a] = basValDouble(op->u.shape.args[a]);
|
||||||
|
|
@ -1238,7 +1259,7 @@ static bool prVecPage(BasPrinterT *p) {
|
||||||
} else if (op->kind == PrOpPictureE) {
|
} else if (op->kind == PrOpPictureE) {
|
||||||
api->vecImage(p->doc, op->u.picture.rgb, op->u.picture.w, op->u.picture.h, op->u.picture.sx, op->u.picture.sy, op->u.picture.sw, op->u.picture.sh, op->u.picture.dx, op->u.picture.dy, op->u.picture.dw, op->u.picture.dh);
|
api->vecImage(p->doc, op->u.picture.rgb, op->u.picture.w, op->u.picture.h, op->u.picture.sx, op->u.picture.sy, op->u.picture.sw, op->u.picture.sh, op->u.picture.dx, op->u.picture.dy, op->u.picture.dw, op->u.picture.dh);
|
||||||
} else {
|
} else {
|
||||||
prShapeAgain(p, op, &surf);
|
prShapeAgain(p, op, &surf, 0, surf.height - 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -61,7 +61,7 @@ typedef struct BasScreenWinT BasScreenWinT; // the program screen's window (f
|
||||||
// (vm.h) with the compiler, so name-keyed lookups never truncate.
|
// (vm.h) with the compiler, so name-keyed lookups never truncate.
|
||||||
// Longest menu Shortcut / ShortcutText ("Ctrl+Alt+Shift+Backspace"), and
|
// Longest menu Shortcut / ShortcutText ("Ctrl+Alt+Shift+Backspace"), and
|
||||||
// what separates it from the caption in an item's label ("&Open\tCtrl+O").
|
// what separates it from the caption in an item's label ("&Open\tCtrl+O").
|
||||||
#define BAS_MENU_KEY_TEXT_MAX 32
|
#define BAS_MENU_KEY_TEXT_MAX DVX_ACCEL_TEXT_MAX
|
||||||
#define BAS_MENU_KEY_SEP '\t'
|
#define BAS_MENU_KEY_SEP '\t'
|
||||||
|
|
||||||
// Window size for a form that gives no Width/Height; the IDE designer
|
// Window size for a form that gives no Width/Height; the IDE designer
|
||||||
|
|
@ -115,6 +115,8 @@ typedef struct BasScreenWinT BasScreenWinT; // the program screen's window (f
|
||||||
#define BAS_PROP_FONTSTRIKETHRU "FontStrikethru"
|
#define BAS_PROP_FONTSTRIKETHRU "FontStrikethru"
|
||||||
#define BAS_PROP_FONTUNDERLINE "FontUnderline"
|
#define BAS_PROP_FONTUNDERLINE "FontUnderline"
|
||||||
#define BAS_PROP_FORECOLOR "ForeColor"
|
#define BAS_PROP_FORECOLOR "ForeColor"
|
||||||
|
#define BAS_PROP_FULLSCREEN "FullScreen"
|
||||||
|
#define BAS_PROP_GAMEINPUT "GameInput"
|
||||||
#define BAS_PROP_HEIGHT "Height"
|
#define BAS_PROP_HEIGHT "Height"
|
||||||
#define BAS_PROP_HELPTOPIC "HelpTopic"
|
#define BAS_PROP_HELPTOPIC "HelpTopic"
|
||||||
#define BAS_PROP_ICON BAS_FORM_ICON
|
#define BAS_PROP_ICON BAS_FORM_ICON
|
||||||
|
|
@ -189,6 +191,10 @@ const BasPropDescT *basFormRtFindCommonProp(const char *name);
|
||||||
const BasPropDescT *basFormRtFindFormProp(const char *name);
|
const BasPropDescT *basFormRtFindFormProp(const char *name);
|
||||||
const BasPropDescT *basFormRtFindMenuProp(const char *name);
|
const BasPropDescT *basFormRtFindMenuProp(const char *name);
|
||||||
|
|
||||||
|
// True for a form property that is run-time state, never part of a .frm
|
||||||
|
// (Visible, DesignMode, FullScreen ...; the object-typed ones aside).
|
||||||
|
bool basFormRtFormPropRuntimeOnly(const char *name);
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// Font properties
|
// Font properties
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
@ -409,6 +415,28 @@ typedef struct {
|
||||||
char handlerName[BAS_MAX_IDENT]; // e.g. "HandleOkClick"
|
char handlerName[BAS_MAX_IDENT]; // e.g. "HandleOkClick"
|
||||||
} BasEventOverrideT;
|
} BasEventOverrideT;
|
||||||
|
|
||||||
|
// A LoadPicture or LoadPictureData Picture object: the picture's size in
|
||||||
|
// pixels (0 x 0 for the empty picture), and its file name ("" for the
|
||||||
|
// empty picture) or, from LoadPictureData, the bytes of its picture file
|
||||||
|
// (data, dataLen bytes and a NUL after them; NULL for a file's picture).
|
||||||
|
// A property showing one from bytes holds its name instead of a file's:
|
||||||
|
// BAS_PICTURE_DATA_MARK and its place in BasFormRtT.pictures.
|
||||||
|
#define BAS_PICTURE_DATA_MARK '*'
|
||||||
|
|
||||||
|
// True when a picture property's text names a LoadPictureData picture
|
||||||
|
// rather than a file (no file name starts with the mark).
|
||||||
|
static inline bool basPictureIsData(const char *path) {
|
||||||
|
return path[0] == BAS_PICTURE_DATA_MARK;
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
int32_t width;
|
||||||
|
int32_t height;
|
||||||
|
uint8_t *data;
|
||||||
|
int32_t dataLen;
|
||||||
|
char path[];
|
||||||
|
} BasPictureT;
|
||||||
|
|
||||||
typedef struct BasControlT {
|
typedef struct BasControlT {
|
||||||
char name[BAS_MAX_IDENT]; // VB control name (e.g. "Command1")
|
char name[BAS_MAX_IDENT]; // VB control name (e.g. "Command1")
|
||||||
char typeName[BAS_MAX_IDENT]; // VB type name (e.g. "CommandButton")
|
char typeName[BAS_MAX_IDENT]; // VB type name (e.g. "CommandButton")
|
||||||
|
|
@ -524,13 +552,47 @@ typedef struct {
|
||||||
int32_t frmSourceLen;
|
int32_t frmSourceLen;
|
||||||
} BasFrmCacheT;
|
} BasFrmCacheT;
|
||||||
|
|
||||||
// A LoadPicture Picture object: the picture's size in pixels (0 x 0 for
|
// A picture PaintPicture decoded, kept for the next call (formGfx's
|
||||||
// the empty picture) and its file name ("" for the empty picture).
|
// drawPictureLoad): its pixels in the display format then, and the file's
|
||||||
|
// size and time when it was read -- or the LoadPictureData picture it is
|
||||||
|
// (picture; path "").
|
||||||
typedef struct {
|
typedef struct {
|
||||||
int32_t width;
|
uint8_t *pixels;
|
||||||
int32_t height;
|
const BasPictureT *picture;
|
||||||
char path[];
|
int64_t size;
|
||||||
} BasPictureT;
|
int64_t time;
|
||||||
|
int32_t w;
|
||||||
|
int32_t h;
|
||||||
|
int32_t pitch;
|
||||||
|
int32_t bpp;
|
||||||
|
char path[DVX_MAX_PATH];
|
||||||
|
} BasDrawPicT;
|
||||||
|
|
||||||
|
// The task of a runtime whose code runs on any task (BasFormRtT.ownTask):
|
||||||
|
// an installed control file's, shared by every program.
|
||||||
|
#define BAS_RT_ANY_TASK UINT32_MAX
|
||||||
|
|
||||||
|
// What a waiting event is: an event, a control's event (it runs through
|
||||||
|
// the control-event path: Index argument, re-entry guard), or a form's
|
||||||
|
// window closed (the form is unloaded, QueryUnload asked first).
|
||||||
|
typedef enum {
|
||||||
|
BasQueuedEventE,
|
||||||
|
BasQueuedCtrlEventE,
|
||||||
|
BasQueuedCloseE
|
||||||
|
} BasQueuedKindE;
|
||||||
|
|
||||||
|
// An event another task dispatched for a program, kept until the
|
||||||
|
// program's own task runs it (BasFormRtT.queued): what was fired, the
|
||||||
|
// arguments copied.
|
||||||
|
typedef struct {
|
||||||
|
BasFormT *form;
|
||||||
|
BasControlT *obj; // the control, form object, or menu proxy (NULL: none)
|
||||||
|
BasValueT *args; // copies (NULL: none), released once it has run
|
||||||
|
int32_t argCount;
|
||||||
|
BasQueuedKindE kind;
|
||||||
|
char ctrlName[BAS_MAX_IDENT];
|
||||||
|
char eventName[BAS_MAX_IDENT];
|
||||||
|
} BasQueuedEventT;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
AppContextT *ctx; // DVX app context
|
AppContextT *ctx; // DVX app context
|
||||||
|
|
@ -545,6 +607,9 @@ typedef struct {
|
||||||
// loop exits at the next pump so the app doesn't stumble forward
|
// loop exits at the next pump so the app doesn't stumble forward
|
||||||
// with a halted VM.
|
// with a halted VM.
|
||||||
bool terminated;
|
bool terminated;
|
||||||
|
// basFormRtLoadAllForms is loading the program's forms (the other
|
||||||
|
// tasks run between them): it counts as busy (basFormRtBusy).
|
||||||
|
bool formsLoading;
|
||||||
// Set true by hosts that want to handle runtime-error reporting
|
// Set true by hosts that want to handle runtime-error reporting
|
||||||
// themselves (the IDE shows the error in the output pane and
|
// themselves (the IDE shows the error in the output pane and
|
||||||
// navigates the editor to the line). When true,
|
// navigates the editor to the line). When true,
|
||||||
|
|
@ -587,6 +652,9 @@ typedef struct {
|
||||||
GameFramePacerT framePacer;
|
GameFramePacerT framePacer;
|
||||||
int32_t frameHz;
|
int32_t frameHz;
|
||||||
uint64_t frameDueUs;
|
uint64_t frameDueUs;
|
||||||
|
// SHELL: the program waiting for its DOS run, and how the run ended
|
||||||
|
int32_t shellAppId;
|
||||||
|
int32_t shellRc;
|
||||||
// The program screen's window (made by the first console or graphics
|
// The program screen's window (made by the first console or graphics
|
||||||
// statement) and its caption: hosts set title to the program's name.
|
// statement) and its caption: hosts set title to the program's name.
|
||||||
BasScreenWinT *screenWin;
|
BasScreenWinT *screenWin;
|
||||||
|
|
@ -595,6 +663,9 @@ typedef struct {
|
||||||
// (stb_ds array of heap objects; the object is that pointer), kept
|
// (stb_ds array of heap objects; the object is that pointer), kept
|
||||||
// until the runtime is destroyed.
|
// until the runtime is destroyed.
|
||||||
BasPictureT **pictures;
|
BasPictureT **pictures;
|
||||||
|
// PaintPicture's decoded picture files (stb_ds), and their bytes
|
||||||
|
BasDrawPicT *drawPics;
|
||||||
|
int32_t drawPicsBytes;
|
||||||
// Screen.MousePointer = vbHourglass is in force: the busy cursor is
|
// Screen.MousePointer = vbHourglass is in force: the busy cursor is
|
||||||
// put back to the arrow when the runtime is destroyed.
|
// put back to the arrow when the runtime is destroyed.
|
||||||
bool pointerBusy;
|
bool pointerBusy;
|
||||||
|
|
@ -608,6 +679,18 @@ typedef struct {
|
||||||
int32_t screenPointer;
|
int32_t screenPointer;
|
||||||
// The Printer object's pages and document (NULL until it is used).
|
// The Printer object's pages and document (NULL until it is used).
|
||||||
BasPrinterT *printer;
|
BasPrinterT *printer;
|
||||||
|
// A program's handlers run on its own task (the one that made the
|
||||||
|
// runtime; BAS_RT_ANY_TASK: any). An event another task dispatched
|
||||||
|
// (the shell's frame takes the input), and a form's window closed
|
||||||
|
// there, wait in queued (stb_ds, oldest first) for the program's next
|
||||||
|
// turn (basFormRtUpdate) -- except an event whose handler answers
|
||||||
|
// through its arguments (Cancel) and Load / QueryUnload / Unload,
|
||||||
|
// which run where they are fired. While any wait, queueHeld holds one
|
||||||
|
// eventDepth bracket so the forms and controls they name are not
|
||||||
|
// freed.
|
||||||
|
uint32_t ownTask;
|
||||||
|
BasQueuedEventT *queued;
|
||||||
|
bool queueHeld;
|
||||||
} BasFormRtT;
|
} BasFormRtT;
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
@ -625,6 +708,38 @@ void basFormRtLoadAllForms(BasFormRtT *rt, const char *startupFormName);
|
||||||
// Called after bytecode halts to keep the app alive for event handlers.
|
// Called after bytecode halts to keep the app alive for event handlers.
|
||||||
void basFormRtEventLoop(BasFormRtT *rt);
|
void basFormRtEventLoop(BasFormRtT *rt);
|
||||||
|
|
||||||
|
// One turn of a program's event loop on its own task: the events another
|
||||||
|
// task dispatched for it run first (in order), then a DVX frame. Every
|
||||||
|
// pump of a running program (the stub's DoEvents, the IDE's, the
|
||||||
|
// runtime's own loops) is one. False when DVX is shutting down.
|
||||||
|
bool basFormRtUpdate(BasFormRtT *rt);
|
||||||
|
|
||||||
|
// The running task (the task switcher's, found at run time; 0 without
|
||||||
|
// one).
|
||||||
|
uint32_t basFormRtTaskId(void);
|
||||||
|
|
||||||
|
// Let the other tasks run once (no event of this program runs): between
|
||||||
|
// the steps of a long call of the program's (starting it, a compile).
|
||||||
|
void basFormRtYield(void);
|
||||||
|
|
||||||
|
// rt's code runs on the calling task from now on (it was made on another:
|
||||||
|
// the IDE makes a program's runtime in a menu command, then runs it on a
|
||||||
|
// task of its own): the events other tasks dispatch for it wait for its
|
||||||
|
// turn here, and it is this task's current runtime.
|
||||||
|
void basFormRtAdopt(BasFormRtT *rt);
|
||||||
|
|
||||||
|
// True while any program's BASIC code is running (a handler waiting in
|
||||||
|
// DoEvents or a dialog box included), has events waiting for its turn
|
||||||
|
// (its UserControls' too), or is loading its forms at its start: a host
|
||||||
|
// that dispatched an event and waits for the program to be done with it
|
||||||
|
// (a test driving a program) waits until this is false.
|
||||||
|
bool basFormRtBusy(void);
|
||||||
|
|
||||||
|
// True while a handler of rt's (or a native bridge holding its forms and
|
||||||
|
// controls) is on some stack: its eventDepth beyond the bracket its
|
||||||
|
// waiting events hold.
|
||||||
|
bool basFormRtCodeOnStack(const BasFormRtT *rt);
|
||||||
|
|
||||||
// Simple non-debug execution: run bytecode in slices with dvxUpdate
|
// Simple non-debug execution: run bytecode in slices with dvxUpdate
|
||||||
// between slices, then enter the event loop. Used by the standalone stub.
|
// between slices, then enter the event loop. Used by the standalone stub.
|
||||||
void basFormRtRunSimple(BasFormRtT *rt);
|
void basFormRtRunSimple(BasFormRtT *rt);
|
||||||
|
|
@ -658,9 +773,11 @@ WindowT *basFormRtScreenWindow(const BasFormRtT *rt);
|
||||||
// AutoRedraw, ScaleMode, ScaleLeft, ScaleTop, ScaleWidth, ScaleHeight).
|
// AutoRedraw, ScaleMode, ScaleLeft, ScaleTop, ScaleWidth, ScaleHeight).
|
||||||
bool basFormRtIsDrawMember(const char *name, bool isProp);
|
bool basFormRtIsDrawMember(const char *name, bool isProp);
|
||||||
|
|
||||||
// Close all serial/secLink resources and unregister the idle pollers.
|
// Close the serial ports and secure links (comm.bas) the program rt
|
||||||
// Called by basFormRtDestroy and by the stub's _appShutdown force-kill hook.
|
// opened, NULL every one, and drop the idle pollers once none is left.
|
||||||
void basFormRtSerialShutdown(void);
|
// basFormRtDestroy calls it for its program (a force kill reaches that
|
||||||
|
// through basRunHost's kill hook), so other programs' stay open.
|
||||||
|
void basFormRtSerialShutdown(const BasFormRtT *rt);
|
||||||
|
|
||||||
#ifdef DVX_HOST_TEST
|
#ifdef DVX_HOST_TEST
|
||||||
// Test-only: clear every file-scope static so a host test process can run
|
// Test-only: clear every file-scope static so a host test process can run
|
||||||
|
|
@ -955,6 +1072,15 @@ bool basUcIsControlType(const char *typeName);
|
||||||
// host's design controls. NULL when it has none.
|
// host's design controls. NULL when it has none.
|
||||||
const WgtIfaceT *basUcOwnIface(const BasFormRtT *rt, const char *typeName);
|
const WgtIfaceT *basUcOwnIface(const BasFormRtT *rt, const char *typeName);
|
||||||
|
|
||||||
|
// A UserControl has events from another task waiting for its program's
|
||||||
|
// turn (basFormRtBusy counts them).
|
||||||
|
bool basUcBusy(void);
|
||||||
|
|
||||||
|
// A program's turn on its own task (basFormRtUpdate): its UserControls'
|
||||||
|
// events that came on other tasks (their code runs on its program's task
|
||||||
|
// only), then their Resize and Paint.
|
||||||
|
void basUcPump(void);
|
||||||
|
|
||||||
// The runtime is being destroyed: its UserControl classes and the ones it
|
// The runtime is being destroyed: its UserControl classes and the ones it
|
||||||
// registered for design are unregistered (their widgets are gone with the
|
// registered for design are unregistered (their widgets are gone with the
|
||||||
// forms).
|
// forms).
|
||||||
|
|
@ -1046,15 +1172,21 @@ void basFormRtDrawClear(BasFormRtT *rt, BasControlT *ctrl);
|
||||||
// Free a control's drawing state.
|
// Free a control's drawing state.
|
||||||
void basFormRtDrawFree(BasControlT *ctrl);
|
void basFormRtDrawFree(BasControlT *ctrl);
|
||||||
|
|
||||||
|
// Free PaintPicture's kept picture files (the runtime is ending).
|
||||||
|
void basFormRtDrawPicsFree(BasFormRtT *rt);
|
||||||
|
|
||||||
// The object is about to be repainted (shown, resized, Refresh): true
|
// The object is about to be repainted (shown, resized, Refresh): true
|
||||||
// when its Paint event should fire -- a form or PictureBox whose
|
// when its Paint event should fire -- a form or PictureBox whose
|
||||||
// AutoRedraw is not True. One whose program set AutoRedraw = False is
|
// AutoRedraw is not True. One whose program set AutoRedraw = False is
|
||||||
// cleared to its BackColor first (VB); by default drawings are kept.
|
// cleared to its BackColor first (VB); by default drawings are kept.
|
||||||
bool basFormRtDrawWantsPaint(BasControlT *ctrl);
|
bool basFormRtDrawWantsPaint(BasControlT *ctrl);
|
||||||
|
|
||||||
|
// The Picture object LOADPICTUREDATA made that v is, else NULL.
|
||||||
|
BasPictureT *basFormRtPictureData(const BasFormRtT *rt, BasValueT v);
|
||||||
|
|
||||||
// The file name of a Picture object LOADPICTURE made ("" for an empty
|
// The file name of a Picture object LOADPICTURE made ("" for an empty
|
||||||
// picture), NULL when v is not one. Picture properties and PaintPicture
|
// picture), NULL when v is not one. Picture properties and PaintPicture
|
||||||
// take either a file name or such an object.
|
// take a file name or a Picture object of either kind.
|
||||||
const char *basFormRtPicturePath(const BasFormRtT *rt, BasValueT v);
|
const char *basFormRtPicturePath(const BasFormRtT *rt, BasValueT v);
|
||||||
|
|
||||||
// The live control an object reference names, or NULL.
|
// The live control an object reference names, or NULL.
|
||||||
|
|
@ -1082,6 +1214,11 @@ void basFormRtPrinterSetProp(BasFormRtT *rt, const char *name, BasValueT va
|
||||||
// and the Printer's state freed.
|
// and the Printer's state freed.
|
||||||
void basFormRtPrinterEnd(BasFormRtT *rt);
|
void basFormRtPrinterEnd(BasFormRtT *rt);
|
||||||
|
|
||||||
|
// The program was ended by force: a page drawn is thrown away and a
|
||||||
|
// document open abandoned (KillDoc) -- nothing is printed in the kill --
|
||||||
|
// and the Printer's state freed.
|
||||||
|
void basFormRtPrinterKill(BasFormRtT *rt);
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// Between formrt.c and formDesign.c
|
// Between formrt.c and formDesign.c
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,7 @@
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
#define MIN_CTRL_SIZE 8
|
#define MIN_CTRL_SIZE 8
|
||||||
|
#define EMPTY_PICTURE_SIZE 32 // a new Image or ImageButton's size: it has none until it has a picture
|
||||||
#define DSGN_NO_SELECTION (-1) // selectedIdx: the form itself
|
#define DSGN_NO_SELECTION (-1) // selectedIdx: the form itself
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
@ -114,18 +115,12 @@ static WidgetT *containerChildParent(FrmCtrlT *pc) {
|
||||||
|
|
||||||
// Create a real DVX widget for design-time display through libdvx's
|
// Create a real DVX widget for design-time display through libdvx's
|
||||||
// wgtCreateByName (the project's own UserControls first, the designer's
|
// wgtCreateByName (the project's own UserControls first, the designer's
|
||||||
// registration of them: basUcResolveType); design-time refuses
|
// registration of them: basUcResolveType). An Image or ImageButton starts
|
||||||
// WGT_CREATE_PARENT_DATA widgets (Image/ImageButton) since we have no
|
// with an empty picture; its Picture property fills it.
|
||||||
// pixel data.
|
|
||||||
static WidgetT *createDesignWidget(const char *vbTypeName, WidgetT *parent) {
|
static WidgetT *createDesignWidget(const char *vbTypeName, WidgetT *parent) {
|
||||||
const char *wgtName = basUcResolveType(NULL, vbTypeName);
|
const char *wgtName = basUcResolveType(NULL, vbTypeName);
|
||||||
const WgtIfaceT *iface = wgtGetIface(wgtName);
|
|
||||||
|
|
||||||
if (!wgtName || (iface && iface->createSig == WGT_CREATE_PARENT_DATA)) {
|
return wgtName ? wgtCreateByName(wgtName, parent) : NULL;
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
return wgtCreateByName(wgtName, parent);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -534,6 +529,15 @@ void dsgnOnMouse(DsgnStateT *ds, int32_t x, int32_t y, bool drag) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A control made with an empty picture (Image, ImageButton) has no size
|
||||||
|
// of its own until it has one: a size to see and select it by
|
||||||
|
const WgtIfaceT *placed = basCtlIface(cp->typeName);
|
||||||
|
|
||||||
|
if (placed && placed->createSig == WGT_CREATE_PARENT_DATA) {
|
||||||
|
cp->width = EMPTY_PICTURE_SIZE;
|
||||||
|
cp->height = EMPTY_PICTURE_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
if (parentWidget) {
|
if (parentWidget) {
|
||||||
dsgnCreateControlWidget(ds->form, cp, parentWidget);
|
dsgnCreateControlWidget(ds->form, cp, parentWidget);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@
|
||||||
#include "dvxWgt.h"
|
#include "dvxWgt.h"
|
||||||
#include "../compiler/lexer.h"
|
#include "../compiler/lexer.h"
|
||||||
#include "../formrt/formrt.h"
|
#include "../formrt/formrt.h"
|
||||||
#include "../formrt/frmModel.h"
|
#include "../basdev/frmModel.h"
|
||||||
|
|
||||||
#include "stb_ds_wrap.h"
|
#include "stb_ds_wrap.h"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -94,6 +94,8 @@
|
||||||
|
|
||||||
#define IDE_MAX_SOURCE PRJ_MAX_SOURCE // editor capacity and largest loadable project file
|
#define IDE_MAX_SOURCE PRJ_MAX_SOURCE // editor capacity and largest loadable project file
|
||||||
#define IDE_MAX_OUTPUT 32768
|
#define IDE_MAX_OUTPUT 32768
|
||||||
|
#define IDE_RUN_TASK_NAME "DVX BASIC program" // the task a run's program runs on (runModule)
|
||||||
|
#define IDE_BUSY_PUMP_MS 50 // a compile, a debugger snippet, or a Replace All lets DVX go on this often (ideBusyPump)
|
||||||
#define IDE_SOURCE_MARGIN 64
|
#define IDE_SOURCE_MARGIN 64
|
||||||
#define IDE_FULL_NAME_BUF (BAS_MAX_IDENT * 2) // "Obj.Evt" / "Obj_Evt" combined names
|
#define IDE_FULL_NAME_BUF (BAS_MAX_IDENT * 2) // "Obj.Evt" / "Obj_Evt" combined names
|
||||||
#define IDE_TITLE_BUF (PRJ_MAX_NAME + 32) // "DVX BASIC - [name] *"
|
#define IDE_TITLE_BUF (PRJ_MAX_NAME + 32) // "DVX BASIC - [name] *"
|
||||||
|
|
@ -301,9 +303,23 @@ typedef enum {
|
||||||
DbgIdleE, // no program loaded
|
DbgIdleE, // no program loaded
|
||||||
DbgRunningE, // program executing
|
DbgRunningE, // program executing
|
||||||
DbgPausedE, // stopped at breakpoint/step
|
DbgPausedE, // stopped at breakpoint/step
|
||||||
DbgStoppingE // Stop requested; runModule has not unwound yet
|
DbgStoppingE // Stop requested; runBody has not unwound yet
|
||||||
} IdeDebugStateE;
|
} IdeDebugStateE;
|
||||||
|
|
||||||
|
// shellAppTaskStart, found at run time (the host tests have no shell)
|
||||||
|
typedef __typeof__(shellAppTaskStart) *IdeTaskStartFnT;
|
||||||
|
|
||||||
|
// The run in progress (runStart, runBody): the IDE windows it hid, and
|
||||||
|
// the owner of the fonts it added
|
||||||
|
typedef struct {
|
||||||
|
bool hadFormWin;
|
||||||
|
bool hadToolbox;
|
||||||
|
bool hadProps;
|
||||||
|
bool hadCodeWin;
|
||||||
|
bool hadPrjWin;
|
||||||
|
int32_t fontOwner;
|
||||||
|
} IdeRunT;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
int32_t fileIdx; // project file index
|
int32_t fileIdx; // project file index
|
||||||
int32_t codeLine; // line within file's code section (1-based)
|
int32_t codeLine; // line within file's code section (1-based)
|
||||||
|
|
@ -480,6 +496,8 @@ static bool hasProject(void);
|
||||||
static bool hasUnsavedData(void);
|
static bool hasUnsavedData(void);
|
||||||
static void helpQueryHandler(void *ctx);
|
static void helpQueryHandler(void *ctx);
|
||||||
static void helpSetCtrlTopic(const char *typeName);
|
static void helpSetCtrlTopic(const char *typeName);
|
||||||
|
static void ideBusyPump(void *ctx);
|
||||||
|
static void ideBusyPumpEnd(void);
|
||||||
static void ideErrorBox(const char *msg);
|
static void ideErrorBox(const char *msg);
|
||||||
static void idePump(void);
|
static void idePump(void);
|
||||||
static void immPrintCallback(void *ctx, const char *text, bool newline);
|
static void immPrintCallback(void *ctx, const char *text, bool newline);
|
||||||
|
|
@ -609,10 +627,13 @@ static void renameProjectFile(int32_t fileIdx);
|
||||||
static bool renameProjectFileTo(int32_t fileIdx, const char *newName);
|
static bool renameProjectFileTo(int32_t fileIdx, const char *newName);
|
||||||
static int32_t replaceInSections(const char *objFilter, bool caseSensitive);
|
static int32_t replaceInSections(const char *objFilter, bool caseSensitive);
|
||||||
static void reportUnreadable(const char *path);
|
static void reportUnreadable(const char *path);
|
||||||
|
static void runBody(void);
|
||||||
static void runCached(void);
|
static void runCached(void);
|
||||||
static void runFontsAdd(int32_t owner);
|
static void runFontsAdd(int32_t owner);
|
||||||
static void runModule(BasModuleT *mod);
|
static void runModule(BasModuleT *mod);
|
||||||
static BasVmResultE runProgramSlices(BasFormRtT *rt);
|
static BasVmResultE runProgramSlices(BasFormRtT *rt);
|
||||||
|
static bool runStart(BasModuleT *mod);
|
||||||
|
static void runTaskEntry(void *arg);
|
||||||
static bool saveActiveFile(void);
|
static bool saveActiveFile(void);
|
||||||
static bool saveAllModified(void);
|
static bool saveAllModified(void);
|
||||||
static bool saveCurProc(bool discardSkeleton);
|
static bool saveCurProc(bool discardSkeleton);
|
||||||
|
|
@ -707,6 +728,12 @@ static WindowT *sLastFocusWin = NULL; // last focused non-toolbar window
|
||||||
static char sOutputBuf[IDE_MAX_OUTPUT];
|
static char sOutputBuf[IDE_MAX_OUTPUT];
|
||||||
static int32_t sOutputLen = 0;
|
static int32_t sOutputLen = 0;
|
||||||
|
|
||||||
|
static PlatformTicksT sBusyPumpAt = 0; // when ideBusyPump last let DVX go on
|
||||||
|
static bool sBusyPumpBusy = false; // ideBusyPump set the busy state (ideBusyPumpEnd clears it)
|
||||||
|
static const BasDbgPumpT sBusyPump = { ideBusyPump, NULL }; // the debugger's snippets pump through it
|
||||||
|
|
||||||
|
static IdeRunT sRun;
|
||||||
|
|
||||||
// Procedure view state -- the editor shows one procedure at a time.
|
// Procedure view state -- the editor shows one procedure at a time.
|
||||||
// Each procedure is stored in its own malloc'd buffer. The editor
|
// Each procedure is stored in its own malloc'd buffer. The editor
|
||||||
// swaps directly between buffers with no splicing needed.
|
// swaps directly between buffers with no splicing needed.
|
||||||
|
|
@ -1638,10 +1665,10 @@ static bool compileProject(void) {
|
||||||
IdeConcatE concat = concatProject(&project, &validatorCtx, true, &sProject.sourceMap, &failIdx);
|
IdeConcatE concat = concatProject(&project, &validatorCtx, true, &sProject.sourceMap, &failIdx);
|
||||||
|
|
||||||
if (concat == ConcatUnreadableE) {
|
if (concat == ConcatUnreadableE) {
|
||||||
sOutputLen = frmEmitClamped(sOutputBuf, IDE_MAX_OUTPUT, 0, "COMPILE ERROR:\nCannot read %s (missing or larger than %d bytes).\n", sProject.files[failIdx].path, (int)IDE_MAX_SOURCE);
|
sOutputLen = dvxStrAppendfCut(sOutputBuf, IDE_MAX_OUTPUT, 0, "COMPILE ERROR:\nCannot read %s (missing or larger than %d bytes).\n", sProject.files[failIdx].path, (int)IDE_MAX_SOURCE);
|
||||||
showCompileError("Compilation failed: cannot read file.");
|
showCompileError("Compilation failed: cannot read file.");
|
||||||
} else if (concat == ConcatTooLargeE) {
|
} else if (concat == ConcatTooLargeE) {
|
||||||
sOutputLen = frmEmitClamped(sOutputBuf, IDE_MAX_OUTPUT, 0, "COMPILE ERROR:\nProject source exceeds %d bytes at %s.\n", (int)IDE_MAX_SOURCE, sProject.files[failIdx].path);
|
sOutputLen = dvxStrAppendfCut(sOutputBuf, IDE_MAX_OUTPUT, 0, "COMPILE ERROR:\nProject source exceeds %d bytes at %s.\n", (int)IDE_MAX_SOURCE, sProject.files[failIdx].path);
|
||||||
showCompileError("Compilation failed: source too large.");
|
showCompileError("Compilation failed: source too large.");
|
||||||
} else if (concat == ConcatFailedE) {
|
} else if (concat == ConcatFailedE) {
|
||||||
setStatus(project.error);
|
setStatus(project.error);
|
||||||
|
|
@ -1674,14 +1701,14 @@ static bool compileProject(void) {
|
||||||
// DXE. Those would silently drop the control at runtime and
|
// DXE. Those would silently drop the control at runtime and
|
||||||
// produce a misleading "control not found" error on first access.
|
// produce a misleading "control not found" error on first access.
|
||||||
if (arrlen(validatorCtx.badTypes) > 0) {
|
if (arrlen(validatorCtx.badTypes) > 0) {
|
||||||
int32_t n = frmEmitClamped(sOutputBuf, IDE_MAX_OUTPUT, 0, "COMPILE ERROR:\nUnknown widget type(s) in .frm files:\n");
|
int32_t n = dvxStrAppendfCut(sOutputBuf, IDE_MAX_OUTPUT, 0, "COMPILE ERROR:\nUnknown widget type(s) in .frm files:\n");
|
||||||
|
|
||||||
for (int32_t i = 0; i < (int32_t)arrlen(validatorCtx.badTypes) && n < IDE_MAX_OUTPUT - IDE_OUTPUT_LINE_RESERVE; i++) {
|
for (int32_t i = 0; i < (int32_t)arrlen(validatorCtx.badTypes) && n < IDE_MAX_OUTPUT - IDE_OUTPUT_LINE_RESERVE; i++) {
|
||||||
BasBadTypeT *b = &validatorCtx.badTypes[i];
|
BasBadTypeT *b = &validatorCtx.badTypes[i];
|
||||||
n = frmEmitClamped(sOutputBuf, IDE_MAX_OUTPUT, n, " " BAS_VALIDATE_BAD_TYPE_FMT "\n", b->formName, b->ctrlName, b->typeName);
|
n = dvxStrAppendfCut(sOutputBuf, IDE_MAX_OUTPUT, n, " " BAS_VALIDATE_BAD_TYPE_FMT "\n", b->formName, b->ctrlName, b->typeName);
|
||||||
}
|
}
|
||||||
|
|
||||||
n = frmEmitClamped(sOutputBuf, IDE_MAX_OUTPUT, n, "\n" BAS_VALIDATE_BAD_TYPE_HINT "\n");
|
n = dvxStrAppendfCut(sOutputBuf, IDE_MAX_OUTPUT, n, "\n" BAS_VALIDATE_BAD_TYPE_HINT "\n");
|
||||||
sOutputLen = n;
|
sOutputLen = n;
|
||||||
showCompileError("Compilation failed: unknown widget type.");
|
showCompileError("Compilation failed: unknown widget type.");
|
||||||
basBuildFreeProject(&project);
|
basBuildFreeProject(&project);
|
||||||
|
|
@ -1697,7 +1724,7 @@ static bool compileProject(void) {
|
||||||
|
|
||||||
basValidateCallbacks(&validatorCtx, &validator);
|
basValidateCallbacks(&validatorCtx, &validator);
|
||||||
|
|
||||||
BasModuleT *mod = basBuildCompile(src, srcLen, sProject.optionExplicit, &validator, &validatorCtx.compiler, &err);
|
BasModuleT *mod = basBuildCompile(src, srcLen, sProject.optionExplicit, &validator, &validatorCtx.compiler, &err, ideBusyPump, NULL);
|
||||||
|
|
||||||
basBuildFreeProject(&project);
|
basBuildFreeProject(&project);
|
||||||
basValidateFree(&validatorCtx);
|
basValidateFree(&validatorCtx);
|
||||||
|
|
@ -1748,9 +1775,9 @@ static bool compileProject(void) {
|
||||||
const char *msg = basBuildErrorText(err.message);
|
const char *msg = basBuildErrorText(err.message);
|
||||||
|
|
||||||
if (procName[0]) {
|
if (procName[0]) {
|
||||||
sOutputLen = frmEmitClamped(sOutputBuf, IDE_MAX_OUTPUT, 0, "COMPILE ERROR:\n%s line %d: %s\n", procName, (int)procLine, msg);
|
sOutputLen = dvxStrAppendfCut(sOutputBuf, IDE_MAX_OUTPUT, 0, "COMPILE ERROR:\n%s line %d: %s\n", procName, (int)procLine, msg);
|
||||||
} else {
|
} else {
|
||||||
sOutputLen = frmEmitClamped(sOutputBuf, IDE_MAX_OUTPUT, 0, "COMPILE ERROR:\n%s line %d: %s\n", errFile, (int)errLocalLine, msg);
|
sOutputLen = dvxStrAppendfCut(sOutputBuf, IDE_MAX_OUTPUT, 0, "COMPILE ERROR:\n%s line %d: %s\n", errFile, (int)errLocalLine, msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
showCompileError("Compilation failed.");
|
showCompileError("Compilation failed.");
|
||||||
|
|
@ -2027,8 +2054,8 @@ static void debugStartOrResume(int32_t cmd) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// debugStop -- request the running program to stop. runModule's loops and
|
// debugStop -- request the running program to stop. runBody's loops and
|
||||||
// doEventsCallback observe sStopRequested and unwind; until runModule has
|
// doEventsCallback observe sStopRequested and unwind; until runBody has
|
||||||
// returned the state stays DbgStoppingE, so nothing can start a new run
|
// returned the state stays DbgStoppingE, so nothing can start a new run
|
||||||
// or free the module the unwinding VM is still executing.
|
// or free the module the unwinding VM is still executing.
|
||||||
static void debugStop(void) {
|
static void debugStop(void) {
|
||||||
|
|
@ -2136,7 +2163,7 @@ static void designCtlRefresh(void) {
|
||||||
if (count > 0 && concatProject(&ps, NULL, false, &map, &failIdx) == ConcatOkE) {
|
if (count > 0 && concatProject(&ps, NULL, false, &map, &failIdx) == ConcatOkE) {
|
||||||
BasCompileErrorT err;
|
BasCompileErrorT err;
|
||||||
|
|
||||||
module = basBuildCompile(ps.source, ps.sourceLen, sProject.optionExplicit, NULL, NULL, &err);
|
module = basBuildCompile(ps.source, ps.sourceLen, sProject.optionExplicit, NULL, NULL, &err, NULL, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
basBuildFreeProject(&ps);
|
basBuildFreeProject(&ps);
|
||||||
|
|
@ -2560,15 +2587,15 @@ static void ensureProjectWindow(bool raise) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// evaluateImmediate -- compile and execute a single line from the
|
|
||||||
// Immediate window. If the line doesn't start with a statement keyword,
|
|
||||||
// wrap it in PRINT so expressions produce visible output.
|
|
||||||
|
|
||||||
// evaluateImmediate -- run a line typed into the Immediate window (an
|
// evaluateImmediate -- run a line typed into the Immediate window (an
|
||||||
// assignment to the paused program's variable, a statement, or an
|
// assignment to the paused program's variable, a statement, or an
|
||||||
// expression to print); an assignment refreshes the debug windows.
|
// expression to print); an assignment refreshes the debug windows.
|
||||||
static void evaluateImmediate(const char *expr) {
|
static void evaluateImmediate(const char *expr) {
|
||||||
if (basDbgExec(sDbgState == DbgPausedE ? sVm : NULL, expr, immPrintCallback, NULL)) {
|
bool assigned = basDbgExec(sDbgState == DbgPausedE ? sVm : NULL, expr, immPrintCallback, NULL, &sBusyPump);
|
||||||
|
|
||||||
|
ideBusyPumpEnd();
|
||||||
|
|
||||||
|
if (assigned) {
|
||||||
updateLocalsWindow();
|
updateLocalsWindow();
|
||||||
updateWatchWindow();
|
updateWatchWindow();
|
||||||
}
|
}
|
||||||
|
|
@ -3570,6 +3597,39 @@ static void helpSetCtrlTopic(const char *typeName) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ideBusyPump -- basBuildCompile's unit callback, the debugger's snippet
|
||||||
|
// pump (sBusyPump), and Replace All's between procedures: a frame every
|
||||||
|
// IDE_BUSY_PUMP_MS, so DVX goes on through a long compile, snippet, or
|
||||||
|
// replace. The busy state keeps the user's input out meanwhile; set here
|
||||||
|
// when the caller had not (ideBusyPumpEnd clears it).
|
||||||
|
static void ideBusyPump(void *ctx) {
|
||||||
|
(void)ctx;
|
||||||
|
|
||||||
|
if (platformClock() - sBusyPumpAt < PLATFORM_MS_TO_TICKS(IDE_BUSY_PUMP_MS)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!sAc->busy) {
|
||||||
|
dvxSetBusy(sAc, true);
|
||||||
|
sBusyPumpBusy = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
dvxUpdate(sAc);
|
||||||
|
sBusyPumpAt = platformClock();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ideBusyPumpEnd -- after a step ideBusyPump may have pumped: the busy
|
||||||
|
// state it set goes.
|
||||||
|
static void ideBusyPumpEnd(void) {
|
||||||
|
if (sBusyPumpBusy) {
|
||||||
|
dvxSetBusy(sAc, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
sBusyPumpBusy = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// ideErrorBox -- modal error message. The busy state swallows all input,
|
// ideErrorBox -- modal error message. The busy state swallows all input,
|
||||||
// so it is cleared first or the box could never be dismissed.
|
// so it is cleared first or the box could never be dismissed.
|
||||||
static void ideErrorBox(const char *msg) {
|
static void ideErrorBox(const char *msg) {
|
||||||
|
|
@ -3579,11 +3639,16 @@ static void ideErrorBox(const char *msg) {
|
||||||
|
|
||||||
|
|
||||||
// idePump -- one pass of the GUI event loop while a program runs, with
|
// idePump -- one pass of the GUI event loop while a program runs, with
|
||||||
// its buffered output shown first
|
// its buffered output shown first (and the events waiting for it run)
|
||||||
static void idePump(void) {
|
static void idePump(void) {
|
||||||
flushOutput();
|
flushOutput();
|
||||||
|
|
||||||
|
if (sDbgFormRt) {
|
||||||
|
basFormRtUpdate(sDbgFormRt);
|
||||||
|
} else {
|
||||||
dvxUpdate(sAc);
|
dvxUpdate(sAc);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
@ -4310,7 +4375,7 @@ static void makePackage(void) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const char *result = idePackageMake(sAc, &sProject, makeOutput);
|
const char *result = idePackageMake(sAc, &sProject, makeOutput, setStatus);
|
||||||
|
|
||||||
if (result) {
|
if (result) {
|
||||||
setStatus(result);
|
setStatus(result);
|
||||||
|
|
@ -4609,7 +4674,7 @@ static void onBreakpointHit(void *ctx, int32_t line) {
|
||||||
// handler finishes; that is not a real pause point (every genuine pause --
|
// handler finishes; that is not a real pause point (every genuine pause --
|
||||||
// breakpoint or Step Into/Over/Out -- arrives with a positive line), so
|
// breakpoint or Step Into/Over/Out -- arrives with a positive line), so
|
||||||
// ignore it. Otherwise Continue would re-pause and require a second press.
|
// ignore it. Otherwise Continue would re-pause and require a second press.
|
||||||
// After Stop, a handler still run while runModule unwinds never pauses:
|
// After Stop, a handler still run while runBody unwinds never pauses:
|
||||||
// the state stays Stopping and no debugger UI is driven.
|
// the state stays Stopping and no debugger UI is driven.
|
||||||
if (line < 0 || sStopRequested || sDbgState == DbgStoppingE) {
|
if (line < 0 || sStopRequested || sDbgState == DbgStoppingE) {
|
||||||
return;
|
return;
|
||||||
|
|
@ -4745,8 +4810,8 @@ static void onClose(WindowT *win) {
|
||||||
// win itself is destroyed last, below, once nothing refers to it
|
// win itself is destroyed last, below, once nothing refers to it
|
||||||
sWin = NULL;
|
sWin = NULL;
|
||||||
|
|
||||||
// A running program is still executing sCachedModule (this close
|
// A running program is still executing sCachedModule (on its task, or
|
||||||
// arrived through its event pump); runModule frees it on exit.
|
// this close arrived through its event pump); runBody frees it on exit.
|
||||||
if (sCachedModule && !sVm) {
|
if (sCachedModule && !sVm) {
|
||||||
basModuleFree(sCachedModule);
|
basModuleFree(sCachedModule);
|
||||||
sCachedModule = NULL;
|
sCachedModule = NULL;
|
||||||
|
|
@ -5643,6 +5708,9 @@ static void onReplaceAll(WidgetT *w) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A long replace let DVX go on (replaceInSections pumps per procedure)
|
||||||
|
ideBusyPumpEnd();
|
||||||
|
|
||||||
char statusBuf[BAS_MAX_IDENT];
|
char statusBuf[BAS_MAX_IDENT];
|
||||||
snprintf(statusBuf, sizeof(statusBuf), "%d replacement(s) made.", (int)totalCount);
|
snprintf(statusBuf, sizeof(statusBuf), "%d replacement(s) made.", (int)totalCount);
|
||||||
setStatus(statusBuf);
|
setStatus(statusBuf);
|
||||||
|
|
@ -6830,6 +6898,7 @@ static int32_t replaceInSections(const char *objFilter, bool caseSensitive) {
|
||||||
|
|
||||||
showProc(idx);
|
showProc(idx);
|
||||||
count += wgtTextAreaReplaceAll(sEditor, sFindText, sReplaceText, caseSensitive);
|
count += wgtTextAreaReplaceAll(sEditor, sFindText, sReplaceText, caseSensitive);
|
||||||
|
ideBusyPump(NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
arrfree(targets);
|
arrfree(targets);
|
||||||
|
|
@ -6846,144 +6915,12 @@ static void reportUnreadable(const char *path) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static void runCached(void) {
|
// runBody -- the rest of a run (runModule), on the task the program runs
|
||||||
if (!sCachedModule) {
|
// on: its forms loaded and shown, its code run, then its event loop until
|
||||||
setStatus("No compiled program. Press F5 to compile first.");
|
// it ends; the error it ended on reported, the IDE's windows restored.
|
||||||
return;
|
static void runBody(void) {
|
||||||
}
|
BasFormRtT *formRt = sDbgFormRt;
|
||||||
|
BasVmT *vm = sVm;
|
||||||
// The forms are registered from the project buffers
|
|
||||||
if (!stashCurrentFile()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
clearOutput();
|
|
||||||
runModule(sCachedModule);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// The project's fonts added for owner (the IDE) while a program runs; one
|
|
||||||
// that cannot be added is reported in the Output window
|
|
||||||
static void runFontsAdd(int32_t owner) {
|
|
||||||
for (int32_t i = 0; i < prjFontCount(&sProject); i++) {
|
|
||||||
char path[DVX_MAX_PATH];
|
|
||||||
|
|
||||||
platformPathJoin(path, sizeof(path), sProject.projectDir, prjFontPath(&sProject, i));
|
|
||||||
|
|
||||||
if (!dvxFontAddFile(owner, path)) {
|
|
||||||
char msg[DVX_MAX_PATH + IDE_MSG_BUF];
|
|
||||||
|
|
||||||
snprintf(msg, sizeof(msg), "Cannot use the font %s.\n", path);
|
|
||||||
outputAppend(msg, (int32_t)strlen(msg));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static void runModule(BasModuleT *mod) {
|
|
||||||
// Never nest a run: a stopped program may still be unwinding
|
|
||||||
if (sVm) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (prjIsControlProject(&sProject)) {
|
|
||||||
setStatus(IDE_CTL_NO_RUN);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// The program's VM and form runtime. In the IDE, App.Path is the
|
|
||||||
// project's folder, App.Data its DATA folder (what the program will
|
|
||||||
// ship), and App.User the user folder the project's program gets
|
|
||||||
// once installed beside it; the program's screen window carries
|
|
||||||
// the project's name and F1 opens the project's help file. The IDE
|
|
||||||
// reports runtime errors through the Output pane and jumps the editor
|
|
||||||
// to the faulting line, so the form runtime's modal error box is
|
|
||||||
// suppressed (its nested event pump raced with events still queued
|
|
||||||
// by the program: "press OK twice").
|
|
||||||
char helpPath[DVX_MAX_PATH] = "";
|
|
||||||
const char *hostError = NULL;
|
|
||||||
BasRunSpecT spec;
|
|
||||||
|
|
||||||
if (sProject.helpFile[0]) {
|
|
||||||
platformPathJoin(helpPath, sizeof(helpPath), sProject.projectDir, sProject.helpFile);
|
|
||||||
}
|
|
||||||
|
|
||||||
memset(&spec, 0, sizeof(spec));
|
|
||||||
spec.appDir = sProject.projectDir;
|
|
||||||
spec.appFile = sProject.projectPath;
|
|
||||||
spec.args = sProject.commandArgs;
|
|
||||||
spec.title = sProject.name;
|
|
||||||
spec.helpFile = helpPath[0] ? helpPath : NULL;
|
|
||||||
spec.printFn = printCallback;
|
|
||||||
spec.inputFn = inputCallback;
|
|
||||||
spec.doEventsFn = doEventsCallback;
|
|
||||||
spec.suppressErrorDialog = true;
|
|
||||||
|
|
||||||
BasFormRtT *formRt = basRunHostCreate(sAc, mod, &spec, &hostError);
|
|
||||||
|
|
||||||
if (!formRt) {
|
|
||||||
setStatus(hostError);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
BasVmT *vm = formRt->vm;
|
|
||||||
|
|
||||||
setStatus("Running...");
|
|
||||||
closeFindDialog();
|
|
||||||
|
|
||||||
// Hide the designer, code and project windows while the program runs;
|
|
||||||
// they are restored (where they were visible) when it ends.
|
|
||||||
bool hadFormWin = sFormWin && sFormWin->visible;
|
|
||||||
bool hadToolbox = sToolboxWin && sToolboxWin->visible;
|
|
||||||
bool hadProps = sPropsWin && sPropsWin->visible;
|
|
||||||
bool hadCodeWin = sCodeWin && sCodeWin->visible;
|
|
||||||
bool hadPrjWin = sProjectWin && sProjectWin->visible;
|
|
||||||
|
|
||||||
if (sFormWin) {
|
|
||||||
dvxHideWindow(sAc, sFormWin);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sToolboxWin) {
|
|
||||||
dvxHideWindow(sAc, sToolboxWin);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sPropsWin) {
|
|
||||||
dvxHideWindow(sAc, sPropsWin);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sCodeWin) {
|
|
||||||
dvxHideWindow(sAc, sCodeWin);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sProjectWin) {
|
|
||||||
dvxHideWindow(sAc, sProjectWin);
|
|
||||||
}
|
|
||||||
|
|
||||||
basVmSetBreakpointCallback(vm, onBreakpointHit, NULL);
|
|
||||||
|
|
||||||
sVm = vm;
|
|
||||||
sDbgFormRt = formRt;
|
|
||||||
sDbgModule = mod;
|
|
||||||
sDbgState = DbgRunningE;
|
|
||||||
sStopRequested = false;
|
|
||||||
|
|
||||||
updateProjectMenuState();
|
|
||||||
|
|
||||||
// Set breakpoints BEFORE loading forms so breakpoints in form
|
|
||||||
// init code (module-level statements inside BEGINFORM) fire.
|
|
||||||
if (sDbgEnabled) {
|
|
||||||
syncVmBreakpoints();
|
|
||||||
|
|
||||||
if (sDbgBreakOnStart) {
|
|
||||||
basVmStepInto(vm);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// The project's fonts, for the program run here as for the built one
|
|
||||||
// (added for the IDE until the run ends)
|
|
||||||
int32_t fontOwner = sAc->currentAppId;
|
|
||||||
|
|
||||||
runFontsAdd(fontOwner);
|
|
||||||
|
|
||||||
// Load forms from project files (AFTER breakpoints are set so
|
// Load forms from project files (AFTER breakpoints are set so
|
||||||
// init code breakpoints fire), then show the startup form.
|
// init code breakpoints fire), then show the startup form.
|
||||||
|
|
@ -7094,7 +7031,7 @@ static void runModule(BasModuleT *mod) {
|
||||||
|
|
||||||
// The fonts the run added (the project's and any the program added):
|
// The fonts the run added (the project's and any the program added):
|
||||||
// the IDE carries none of its own
|
// the IDE carries none of its own
|
||||||
dvxFontRemoveOwner(fontOwner);
|
dvxFontRemoveOwner(sRun.fontOwner);
|
||||||
|
|
||||||
// If the IDE was closed while the program was running, skip all UI
|
// If the IDE was closed while the program was running, skip all UI
|
||||||
// updates -- the windows are already destroyed -- and release the
|
// updates -- the windows are already destroyed -- and release the
|
||||||
|
|
@ -7128,23 +7065,23 @@ static void runModule(BasModuleT *mod) {
|
||||||
dvxRaiseWindow(sAc, sCodeWin);
|
dvxRaiseWindow(sAc, sCodeWin);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (hadFormWin && sFormWin) {
|
if (sRun.hadFormWin && sFormWin) {
|
||||||
dvxShowWindow(sAc, sFormWin);
|
dvxShowWindow(sAc, sFormWin);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hadToolbox && sToolboxWin) {
|
if (sRun.hadToolbox && sToolboxWin) {
|
||||||
dvxShowWindow(sAc, sToolboxWin);
|
dvxShowWindow(sAc, sToolboxWin);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hadProps && sPropsWin) {
|
if (sRun.hadProps && sPropsWin) {
|
||||||
dvxShowWindow(sAc, sPropsWin);
|
dvxShowWindow(sAc, sPropsWin);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hadCodeWin && sCodeWin) {
|
if (sRun.hadCodeWin && sCodeWin) {
|
||||||
dvxShowWindow(sAc, sCodeWin);
|
dvxShowWindow(sAc, sCodeWin);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hadPrjWin && sProjectWin) {
|
if (sRun.hadPrjWin && sProjectWin) {
|
||||||
dvxShowWindow(sAc, sProjectWin);
|
dvxShowWindow(sAc, sProjectWin);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -7154,6 +7091,58 @@ static void runModule(BasModuleT *mod) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void runCached(void) {
|
||||||
|
if (!sCachedModule) {
|
||||||
|
setStatus("No compiled program. Press F5 to compile first.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The forms are registered from the project buffers
|
||||||
|
if (!stashCurrentFile()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
clearOutput();
|
||||||
|
runModule(sCachedModule);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// The project's fonts added for owner (the IDE) while a program runs; one
|
||||||
|
// that cannot be added is reported in the Output window
|
||||||
|
static void runFontsAdd(int32_t owner) {
|
||||||
|
for (int32_t i = 0; i < prjFontCount(&sProject); i++) {
|
||||||
|
char path[DVX_MAX_PATH];
|
||||||
|
|
||||||
|
platformPathJoin(path, sizeof(path), sProject.projectDir, prjFontPath(&sProject, i));
|
||||||
|
|
||||||
|
if (!dvxFontAddFile(owner, path)) {
|
||||||
|
char msg[DVX_MAX_PATH + IDE_MSG_BUF];
|
||||||
|
|
||||||
|
snprintf(msg, sizeof(msg), "Cannot use the font %s.\n", path);
|
||||||
|
outputAppend(msg, (int32_t)strlen(msg));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// runModule -- run mod: set up here (runStart), then run (runBody) on a
|
||||||
|
// task of the IDE's own (shellAppTaskStart), so the shell's main loop
|
||||||
|
// goes on while the program does -- programs that end are reaped, Exit
|
||||||
|
// and DOS requests carried out, the screen saver started. Without one
|
||||||
|
// (the host tests have no shell) the program runs here, to its end.
|
||||||
|
static void runModule(BasModuleT *mod) {
|
||||||
|
if (!runStart(mod)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
IdeTaskStartFnT taskStart = (IdeTaskStartFnT)dlsym(NULL, DVX_SYM("shellAppTaskStart"));
|
||||||
|
|
||||||
|
if (!taskStart || taskStart(sAc, IDE_RUN_TASK_NAME, BAS_RUN_STACK_SIZE, runTaskEntry, NULL) < 0) {
|
||||||
|
runBody();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// runProgramSlices -- run the program in step slices, yielding to DVX
|
// runProgramSlices -- run the program in step slices, yielding to DVX
|
||||||
// between them, pausing at breakpoints, until it halts, fails (its
|
// between them, pausing at breakpoints, until it halts, fails (its
|
||||||
// message and line left in the VM, see basRunHostSlice) or is stopped.
|
// message and line left in the VM, see basRunHostSlice) or is stopped.
|
||||||
|
|
@ -7195,6 +7184,126 @@ static BasVmResultE runProgramSlices(BasFormRtT *rt) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// runStart -- the first part of a run (runModule): the program's runtime
|
||||||
|
// made, the IDE's windows hidden, breakpoints set (and stepping when the
|
||||||
|
// run breaks on start), the project's fonts added. False when it cannot
|
||||||
|
// start.
|
||||||
|
static bool runStart(BasModuleT *mod) {
|
||||||
|
// Never nest a run: a stopped program may still be unwinding
|
||||||
|
if (sVm) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (prjIsControlProject(&sProject)) {
|
||||||
|
setStatus(IDE_CTL_NO_RUN);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The program's VM and form runtime. In the IDE, App.Path is the
|
||||||
|
// project's folder, App.Data its DATA folder (what the program will
|
||||||
|
// ship), and App.User the user folder the project's program gets
|
||||||
|
// once installed beside it; the program's screen window carries
|
||||||
|
// the project's name and F1 opens the project's help file. The IDE
|
||||||
|
// reports runtime errors through the Output pane and jumps the editor
|
||||||
|
// to the faulting line, so the form runtime's modal error box is
|
||||||
|
// suppressed (its nested event pump raced with events still queued
|
||||||
|
// by the program: "press OK twice").
|
||||||
|
char helpPath[DVX_MAX_PATH] = "";
|
||||||
|
const char *hostError = NULL;
|
||||||
|
BasRunSpecT spec;
|
||||||
|
|
||||||
|
if (sProject.helpFile[0]) {
|
||||||
|
platformPathJoin(helpPath, sizeof(helpPath), sProject.projectDir, sProject.helpFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
memset(&spec, 0, sizeof(spec));
|
||||||
|
spec.appDir = sProject.projectDir;
|
||||||
|
spec.appFile = sProject.projectPath;
|
||||||
|
spec.args = sProject.commandArgs;
|
||||||
|
spec.title = sProject.name;
|
||||||
|
spec.helpFile = helpPath[0] ? helpPath : NULL;
|
||||||
|
spec.printFn = printCallback;
|
||||||
|
spec.inputFn = inputCallback;
|
||||||
|
spec.doEventsFn = doEventsCallback;
|
||||||
|
spec.suppressErrorDialog = true;
|
||||||
|
|
||||||
|
BasFormRtT *formRt = basRunHostCreate(sAc, mod, &spec, &hostError);
|
||||||
|
|
||||||
|
if (!formRt) {
|
||||||
|
setStatus(hostError);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
BasVmT *vm = formRt->vm;
|
||||||
|
|
||||||
|
setStatus("Running...");
|
||||||
|
closeFindDialog();
|
||||||
|
|
||||||
|
// Hide the designer, code and project windows while the program runs;
|
||||||
|
// they are restored (where they were visible) when it ends.
|
||||||
|
sRun.hadFormWin = sFormWin && sFormWin->visible;
|
||||||
|
sRun.hadToolbox = sToolboxWin && sToolboxWin->visible;
|
||||||
|
sRun.hadProps = sPropsWin && sPropsWin->visible;
|
||||||
|
sRun.hadCodeWin = sCodeWin && sCodeWin->visible;
|
||||||
|
sRun.hadPrjWin = sProjectWin && sProjectWin->visible;
|
||||||
|
|
||||||
|
if (sFormWin) {
|
||||||
|
dvxHideWindow(sAc, sFormWin);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sToolboxWin) {
|
||||||
|
dvxHideWindow(sAc, sToolboxWin);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sPropsWin) {
|
||||||
|
dvxHideWindow(sAc, sPropsWin);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sCodeWin) {
|
||||||
|
dvxHideWindow(sAc, sCodeWin);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sProjectWin) {
|
||||||
|
dvxHideWindow(sAc, sProjectWin);
|
||||||
|
}
|
||||||
|
|
||||||
|
basVmSetBreakpointCallback(vm, onBreakpointHit, NULL);
|
||||||
|
|
||||||
|
sVm = vm;
|
||||||
|
sDbgFormRt = formRt;
|
||||||
|
sDbgModule = mod;
|
||||||
|
sDbgState = DbgRunningE;
|
||||||
|
sStopRequested = false;
|
||||||
|
|
||||||
|
updateProjectMenuState();
|
||||||
|
|
||||||
|
// Set breakpoints BEFORE loading forms so breakpoints in form
|
||||||
|
// init code (module-level statements inside BEGINFORM) fire.
|
||||||
|
if (sDbgEnabled) {
|
||||||
|
syncVmBreakpoints();
|
||||||
|
|
||||||
|
if (sDbgBreakOnStart) {
|
||||||
|
basVmStepInto(vm);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The project's fonts, for the program run here as for the built one
|
||||||
|
// (added for the IDE until the run ends)
|
||||||
|
sRun.fontOwner = sAc->currentAppId;
|
||||||
|
runFontsAdd(sRun.fontOwner);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// runTaskEntry -- runModule's task: the program's runtime moves to it,
|
||||||
|
// then the run goes on (runBody).
|
||||||
|
static void runTaskEntry(void *arg) {
|
||||||
|
(void)arg;
|
||||||
|
basFormRtAdopt(sDbgFormRt);
|
||||||
|
runBody();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// saveActiveFile -- write the active file (editor/designer state first
|
// saveActiveFile -- write the active file (editor/designer state first
|
||||||
// stashed into its buffer). Returns false when nothing was written.
|
// stashed into its buffer). Returns false when nothing was written.
|
||||||
static bool saveActiveFile(void) {
|
static bool saveActiveFile(void) {
|
||||||
|
|
@ -9017,7 +9126,7 @@ static void updateWatchWindow(void) {
|
||||||
|
|
||||||
valBuf[0] = '\0';
|
valBuf[0] = '\0';
|
||||||
|
|
||||||
if (sDbgState == DbgPausedE && sVm && sDbgModule && !basDbgEval(sVm, sWatchExprs[i], valBuf, sizeof(valBuf))) {
|
if (sDbgState == DbgPausedE && sVm && sDbgModule && !basDbgEval(sVm, sWatchExprs[i], valBuf, sizeof(valBuf), &sBusyPump)) {
|
||||||
snprintf(valBuf, sizeof(valBuf), "%s", BAS_DBG_EVAL_ERROR);
|
snprintf(valBuf, sizeof(valBuf), "%s", BAS_DBG_EVAL_ERROR);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -9025,6 +9134,7 @@ static void updateWatchWindow(void) {
|
||||||
listRowsAdd(&sWatchRows, valBuf);
|
listRowsAdd(&sWatchRows, valBuf);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ideBusyPumpEnd();
|
||||||
listRowsCommit(&sWatchRows, sWatchList, count);
|
listRowsCommit(&sWatchRows, sWatchList, count);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -525,7 +525,7 @@ static void onOk(WidgetT *w) {
|
||||||
frmMenuStripBlank(&sMed.items);
|
frmMenuStripBlank(&sMed.items);
|
||||||
|
|
||||||
char reason[MED_MSG_BUF];
|
char reason[MED_MSG_BUF];
|
||||||
int32_t badIdx = frmModelMenuValidate(sMed.form, sMed.items, basIsValidIdent, reason, sizeof(reason));
|
int32_t badIdx = frmModelMenuValidate(sMed.form, sMed.items, reason, sizeof(reason));
|
||||||
|
|
||||||
if (badIdx >= 0) {
|
if (badIdx >= 0) {
|
||||||
dvxErrorBox(sMed.ctx, MED_DLG_TITLE, reason);
|
dvxErrorBox(sMed.ctx, MED_DLG_TITLE, reason);
|
||||||
|
|
|
||||||
|
|
@ -60,6 +60,7 @@
|
||||||
#define PKG_NEED_SEP "," // between the needs of [Package] Needs
|
#define PKG_NEED_SEP "," // between the needs of [Package] Needs
|
||||||
#define PKG_NEED_BLANKS " \t" // between a need's id and version
|
#define PKG_NEED_BLANKS " \t" // between a need's id and version
|
||||||
#define PKG_ID_DEFAULT_VENDOR "my" // a package name's vendor when the project has no publisher
|
#define PKG_ID_DEFAULT_VENDOR "my" // a package name's vendor when the project has no publisher
|
||||||
|
#define PKG_SLICE_MS 50 // making the package between frames
|
||||||
|
|
||||||
// The keys offered to sign with: their files and the list's lines
|
// The keys offered to sign with: their files and the list's lines
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
|
@ -74,7 +75,8 @@ typedef struct {
|
||||||
|
|
||||||
static bool askId(AppContextT *ctx, PrjStateT *prj);
|
static bool askId(AppContextT *ctx, PrjStateT *prj);
|
||||||
static bool chooseKey(AppContextT *ctx, PrjStateT *prj, char *secretPath, int32_t secretSize);
|
static bool chooseKey(AppContextT *ctx, PrjStateT *prj, char *secretPath, int32_t secretSize);
|
||||||
const char *idePackageMake(AppContextT *ctx, PrjStateT *prj, IdeBuildFnT build);
|
const char *idePackageMake(AppContextT *ctx, PrjStateT *prj, IdeBuildFnT build, IdeStatusFnT status);
|
||||||
|
static const char *jobRun(AppContextT *ctx, PkgDpkJobT *job, IdeStatusFnT status);
|
||||||
static void keysList(KeyListT *keys);
|
static void keysList(KeyListT *keys);
|
||||||
static bool manifestOf(const PrjStateT *prj, PkgManifestT *m);
|
static bool manifestOf(const PrjStateT *prj, PkgManifestT *m);
|
||||||
static const char *say(const char *fmt, ...) __attribute__((format(printf, 1, 2)));
|
static const char *say(const char *fmt, ...) __attribute__((format(printf, 1, 2)));
|
||||||
|
|
@ -149,7 +151,7 @@ static bool chooseKey(AppContextT *ctx, PrjStateT *prj, char *secretPath, int32_
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const char *idePackageMake(AppContextT *ctx, PrjStateT *prj, IdeBuildFnT build) {
|
const char *idePackageMake(AppContextT *ctx, PrjStateT *prj, IdeBuildFnT build, IdeStatusFnT status) {
|
||||||
static const FileFilterT filters[] = {
|
static const FileFilterT filters[] = {
|
||||||
{ "Packages (*.dpk)" },
|
{ "Packages (*.dpk)" },
|
||||||
{ "All Files (*.*)" }
|
{ "All Files (*.*)" }
|
||||||
|
|
@ -166,6 +168,7 @@ const char *idePackageMake(AppContextT *ctx, PrjStateT *prj, IdeBuildFnT build)
|
||||||
PkgManifestT m;
|
PkgManifestT m;
|
||||||
PkgSecretT secret;
|
PkgSecretT secret;
|
||||||
PkgDpkFileT *files = NULL;
|
PkgDpkFileT *files = NULL;
|
||||||
|
PkgDpkJobT *job = NULL;
|
||||||
const char *failure = NULL;
|
const char *failure = NULL;
|
||||||
bool control = prjIsControlProject(prj);
|
bool control = prjIsControlProject(prj);
|
||||||
bool ok;
|
bool ok;
|
||||||
|
|
@ -217,8 +220,9 @@ const char *idePackageMake(AppContextT *ctx, PrjStateT *prj, IdeBuildFnT build)
|
||||||
failure = say("%s", err);
|
failure = say("%s", err);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!failure && !pkgDpkBuildFiles(&m, files, (int32_t)arrlen(files), outPath, secretPath[0] ? &secret : NULL, err, sizeof(err))) {
|
if (!failure) {
|
||||||
failure = say("%s", err);
|
job = pkgDpkBuildStart(&m, files, (int32_t)arrlen(files), outPath, secretPath[0] ? &secret : NULL, err, sizeof(err));
|
||||||
|
failure = job ? jobRun(ctx, job, status) : say("%s", err);
|
||||||
}
|
}
|
||||||
|
|
||||||
pkgSecretWipe(&secret);
|
pkgSecretWipe(&secret);
|
||||||
|
|
@ -234,6 +238,32 @@ const char *idePackageMake(AppContextT *ctx, PrjStateT *prj, IdeBuildFnT build)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Run the package job (freed) to its end, a slice at a time with a frame
|
||||||
|
// of DVX between slices (the busy cursor up: input waits) and how far it
|
||||||
|
// is on the status line: NULL when the package is made, else why not.
|
||||||
|
static const char *jobRun(AppContextT *ctx, PkgDpkJobT *job, IdeStatusFnT status) {
|
||||||
|
PkgDpkStepE step = PkgDpkMoreE;
|
||||||
|
const char *failure;
|
||||||
|
|
||||||
|
dvxSetBusy(ctx, true);
|
||||||
|
|
||||||
|
while (step == PkgDpkMoreE) {
|
||||||
|
PlatformTicksT end = platformClock() + PLATFORM_MS_TO_TICKS(PKG_SLICE_MS);
|
||||||
|
|
||||||
|
while ((step = pkgDpkJobStep(job, PKG_DPK_STEP)) == PkgDpkMoreE && platformClock() < end) {
|
||||||
|
}
|
||||||
|
|
||||||
|
status(say("Making the package: %d%%", (int)pkgDpkJobPercent(job)));
|
||||||
|
dvxUpdate(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
dvxSetBusy(ctx, false);
|
||||||
|
failure = step == PkgDpkDoneE ? NULL : say("%s", pkgDpkJobMessage(job));
|
||||||
|
pkgDpkJobFree(job);
|
||||||
|
return failure;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// The user's secret keys (the KEYS folder of the user folder), by the
|
// The user's secret keys (the KEYS folder of the user folder), by the
|
||||||
// names in them, after "Not signed".
|
// names in them, after "Not signed".
|
||||||
static void keysList(KeyListT *keys) {
|
static void keysList(KeyListT *keys) {
|
||||||
|
|
|
||||||
|
|
@ -45,12 +45,16 @@
|
||||||
// release: NULL when it is built, else why not.
|
// release: NULL when it is built, else why not.
|
||||||
typedef const char *(*IdeBuildFnT)(const char *outPath, bool release);
|
typedef const char *(*IdeBuildFnT)(const char *outPath, bool release);
|
||||||
|
|
||||||
|
// Show text on the IDE's status line.
|
||||||
|
typedef void (*IdeStatusFnT)(const char *text);
|
||||||
|
|
||||||
// Make the project's package: its package name asked for when the
|
// Make the project's package: its package name asked for when the
|
||||||
// project has none (and kept in it, as the key chosen is), the package
|
// project has none (and kept in it, as the key chosen is), the package
|
||||||
// file and the key to sign with asked for, the program built by build
|
// file and the key to sign with asked for, the program built by build
|
||||||
// into a scratch folder, and the package made from it and the project's
|
// into a scratch folder, and the package made from it and the project's
|
||||||
// details ([Project] and [Package]). The status line to show: the package
|
// details ([Project] and [Package]), a step at a time with DVX kept going
|
||||||
// made, or why not; NULL when the user cancelled.
|
// between steps and status showing how far it is. The status line to
|
||||||
const char *idePackageMake(AppContextT *ctx, PrjStateT *prj, IdeBuildFnT build);
|
// show: the package made, or why not; NULL when the user cancelled.
|
||||||
|
const char *idePackageMake(AppContextT *ctx, PrjStateT *prj, IdeBuildFnT build, IdeStatusFnT status);
|
||||||
|
|
||||||
#endif // IDE_PACKAGE_H
|
#endif // IDE_PACKAGE_H
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,7 @@
|
||||||
#include "../basdev/basSource.h"
|
#include "../basdev/basSource.h"
|
||||||
#include "../formrt/formrt.h"
|
#include "../formrt/formrt.h"
|
||||||
#include "../formrt/frmParser.h"
|
#include "../formrt/frmParser.h"
|
||||||
|
#include "../../../../libs/kpunch/sql/dvxSql.h"
|
||||||
#include "dvxDlg.h"
|
#include "dvxDlg.h"
|
||||||
#include "dvxWm.h"
|
#include "dvxWm.h"
|
||||||
#include "box/box.h"
|
#include "box/box.h"
|
||||||
|
|
@ -140,14 +141,14 @@ static const PrpFontWordT sFontWords[] = {
|
||||||
// here; the column-name and table-name probes resolve the subset they use.
|
// here; the column-name and table-name probes resolve the subset they use.
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
typedef int32_t (*SqlOpenFnT)(const char *);
|
typedef __typeof__(dvxSqlOpen) *SqlOpenFnT;
|
||||||
typedef void (*SqlCloseFnT)(int32_t);
|
typedef __typeof__(dvxSqlClose) *SqlCloseFnT;
|
||||||
typedef int32_t (*SqlQueryFnT)(int32_t, const char *);
|
typedef __typeof__(dvxSqlQuery) *SqlQueryFnT;
|
||||||
typedef int32_t (*SqlFieldCountFnT)(int32_t);
|
typedef __typeof__(dvxSqlFieldCount) *SqlFieldCountFnT;
|
||||||
typedef const char *(*SqlFieldNameFnT)(int32_t, int32_t);
|
typedef __typeof__(dvxSqlFieldName) *SqlFieldNameFnT;
|
||||||
typedef bool (*SqlNextFnT)(int32_t);
|
typedef __typeof__(dvxSqlNext) *SqlNextFnT;
|
||||||
typedef const char *(*SqlFieldTextFnT)(int32_t, int32_t);
|
typedef __typeof__(dvxSqlFieldText) *SqlFieldTextFnT;
|
||||||
typedef void (*SqlFreeResultFnT)(int32_t);
|
typedef __typeof__(dvxSqlFreeResult) *SqlFreeResultFnT;
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// Module state
|
// Module state
|
||||||
|
|
@ -215,11 +216,11 @@ static void addFontRow(const WidgetT *w) {
|
||||||
}
|
}
|
||||||
|
|
||||||
basFontChoiceOf(wgtFont(w), &font);
|
basFontChoiceOf(wgtFont(w), &font);
|
||||||
pos = frmEmitClamped(text, sizeof(text), 0, "%s, %" PRId32, font.family, font.size);
|
pos = dvxStrAppendfCut(text, sizeof(text), 0, "%s, %" PRId32, font.family, font.size);
|
||||||
|
|
||||||
for (int32_t i = 0; i < DVX_ARRAY_LEN(sFontWords); i++) {
|
for (int32_t i = 0; i < DVX_ARRAY_LEN(sFontWords); i++) {
|
||||||
if (font.style & sFontWords[i].style) {
|
if (font.style & sFontWords[i].style) {
|
||||||
pos = frmEmitClamped(text, sizeof(text), pos, ", %s", sFontWords[i].word);
|
pos = dvxStrAppendfCut(text, sizeof(text), pos, ", %s", sFontWords[i].word);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -161,12 +161,6 @@ WindowT *tbxCreate(AppContextT *ctx, DsgnStateT *ds, int32_t y) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Types that need pixel data at creation (Image, ImageButton)
|
|
||||||
// cannot be placed at design time.
|
|
||||||
if (iface->createSig == WGT_CREATE_PARENT_DATA) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add tool entry
|
// Add tool entry
|
||||||
TbxToolEntryT entry;
|
TbxToolEntryT entry;
|
||||||
memset(&entry, 0, sizeof(entry));
|
memset(&entry, 0, sizeof(entry));
|
||||||
|
|
|
||||||
|
|
@ -779,7 +779,7 @@ Click a tool to select it (the active tool name is stored in the designer state)
|
||||||
WrapBox Flow layout container that wraps items to the next row.
|
WrapBox Flow layout container that wraps items to the next row.
|
||||||
.endtable
|
.endtable
|
||||||
|
|
||||||
Image and ImageButton controls are not in the Toolbox; they can still be written into a .frm file by hand and are then edited in the Properties panel like any other control.
|
A new Image or ImageButton starts empty, 32 by 32 pixels so it can be seen and selected: set its Picture property to show a picture.
|
||||||
|
|
||||||
.link ide.designer Form Designer
|
.link ide.designer Form Designer
|
||||||
.link ide.usercontrols UserControls in the IDE
|
.link ide.usercontrols UserControls in the IDE
|
||||||
|
|
|
||||||
|
|
@ -1760,6 +1760,7 @@ ErrorHandler:
|
||||||
11 Division by zero
|
11 Division by zero
|
||||||
13 Type mismatch (text that is not a number where a number is needed) / not an array / not a TYPE instance
|
13 Type mismatch (text that is not a number where a number is needed) / not an array / not a TYPE instance
|
||||||
14 Out of string space (no memory to build a string)
|
14 Out of string space (no memory to build a string)
|
||||||
|
18 User interrupt occurred (a control's code ran too long without returning and was stopped)
|
||||||
20 RESUME without error
|
20 RESUME without error
|
||||||
26 FOR loop nesting too deep
|
26 FOR loop nesting too deep
|
||||||
28 Out of stack space (calls or expressions nested too deeply)
|
28 Out of stack space (calls or expressions nested too deeply)
|
||||||
|
|
@ -1800,13 +1801,15 @@ Executes an operating-system command.
|
||||||
SHELL "command"
|
SHELL "command"
|
||||||
.endcode
|
.endcode
|
||||||
|
|
||||||
When used as a function, SHELL returns the exit code of the command.
|
When used as a function, SHELL returns the exit code of the command, or -1 when it did not run.
|
||||||
|
|
||||||
.code
|
.code
|
||||||
Shell "DIR /B"
|
Shell "DIR /B"
|
||||||
exitCode = Shell("COPY A.TXT B.TXT")
|
exitCode = Shell("COPY A.TXT B.TXT")
|
||||||
.endcode
|
.endcode
|
||||||
|
|
||||||
|
DVX steps aside while the command runs, as it does for a DOS program started from Program Manager: the screen goes back to text, and the command has the keyboard, the mouse, and the hardware to itself. Nothing of DVX runs meanwhile, so the other programs that are running are named first and asked to close (your program stays and waits); choosing No, or a program that keeps a window open, means the command does not run (-1). When the command ends, the desktop comes back and your program goes on. A command that prints and ends at once is gone from the screen as soon as it ends: ShellRunDos (shell.bas) can wait for a key first.
|
||||||
|
|
||||||
The command goes to DOS as it is written. DOS commands read / as the start of a switch (DIR /B), so write the paths in a command with \ (SHELL "DEL C:\TEMP\X.TXT"); FileOpsPathNative$ (fileops.bas) turns a path built with / into that form. Everywhere else in DVX BASIC a path may use / or \.
|
The command goes to DOS as it is written. DOS commands read / as the start of a switch (DIR /B), so write the paths in a command with \ (SHELL "DEL C:\TEMP\X.TXT"); FileOpsPathNative$ (fileops.bas) turns a path built with / into that form. Everywhere else in DVX BASIC a path may use / or \.
|
||||||
|
|
||||||
.h2 SLEEP
|
.h2 SLEEP
|
||||||
|
|
@ -1897,6 +1900,8 @@ PLAY "T160 O3 L8 E D C D E E E4"
|
||||||
MF Music foreground: PLAY, SOUND, and BEEP wait until the sound has finished (the default).
|
MF Music foreground: PLAY, SOUND, and BEEP wait until the sound has finished (the default).
|
||||||
MB Music background: the program goes on while the music plays; it only waits when more than 32 notes are waiting.
|
MB Music background: the program goes on while the music plays; it only waits when more than 32 notes are waiting.
|
||||||
I n DVX's own: play the notes that follow on General MIDI instrument n (1 to 128; 1 is the piano, 41 the violin, 74 the flute) through the music synthesizer. I0 goes back to plain tones (the default).
|
I n DVX's own: play the notes that follow on General MIDI instrument n (1 to 128; 1 is the piano, 41 the violin, 74 the flute) through the music synthesizer. I0 goes back to plain tones (the default).
|
||||||
|
V n Volume of the notes that follow, 0 to 100 percent (default 100), as in QB64. On an instrument it sets how hard the note is played.
|
||||||
|
@ n Waveform of the tones that follow, as in QB64: @1 square (the default, like the PC speaker), @3 triangle (softer), @5 noise (drums, explosions). QB64's @2 and @4 are not available.
|
||||||
X "X" + VARPTR$(s$) plays the commands in s$ at that point (s$ may use X itself, up to 16 levels deep).
|
X "X" + VARPTR$(s$) plays the commands in s$ at that point (s$ may use X itself, up to 16 levels deep).
|
||||||
= Any number above may be written as "=" + VARPTR$(n), n a numeric expression: "T=" + VARPTR$(tempo).
|
= Any number above may be written as "=" + VARPTR$(n), n a numeric expression: "T=" + VARPTR$(tempo).
|
||||||
.endtable
|
.endtable
|
||||||
|
|
@ -1909,11 +1914,12 @@ tempo = 160
|
||||||
PLAY "T=" + VARPTR$(tempo) + "O3 X" + VARPTR$(verse$) + "X" + VARPTR$(verse$)
|
PLAY "T=" + VARPTR$(tempo) + "O3 X" + VARPTR$(verse$) + "X" + VARPTR$(verse$)
|
||||||
.endcode
|
.endcode
|
||||||
|
|
||||||
Building the string with + works too, such as PLAY "O" + STR$(n). Without I, the notes are square-wave tones like the PC speaker's; with it, they play on the same synthesizer as MIDI music.
|
Building the string with + works too, such as PLAY "O" + STR$(n). Without I, the notes are tones -- square waves like the PC speaker's unless @ chooses another -- and with it, they play on the same synthesizer as MIDI music. The PC speaker plays only square tones at one volume: there V and @ make no difference, except that V0 is silent.
|
||||||
|
|
||||||
.code
|
.code
|
||||||
PLAY "MB T120 L4 CDEF GAB>C" ' a scale in the background
|
PLAY "MB T120 L4 CDEF GAB>C" ' a scale in the background
|
||||||
PLAY "I74 O4 L8 GAB>C" ' on the flute
|
PLAY "I74 O4 L8 GAB>C" ' on the flute
|
||||||
|
PLAY "@3 V60 L8 CEG>C" ' softer, quieter
|
||||||
.endcode
|
.endcode
|
||||||
|
|
||||||
.h2 PLAY Function
|
.h2 PLAY Function
|
||||||
|
|
@ -2057,8 +2063,11 @@ Shows the program's screen on the whole screen, or back in its window. Alt+Enter
|
||||||
.code
|
.code
|
||||||
FULLSCREEN [ON | OFF]
|
FULLSCREEN [ON | OFF]
|
||||||
FULLSCREEN expression ' True: whole screen, False: window
|
FULLSCREEN expression ' True: whole screen, False: window
|
||||||
|
isFull = FULLSCREEN ' True while the program's screen has the whole screen
|
||||||
.endcode
|
.endcode
|
||||||
|
|
||||||
|
Used as a value, FULLSCREEN tells whether the program's screen has the whole screen (also while the desktop is shown for another program, since the screen comes back with the focus): False when no suitable video mode could be set, or before SCREEN or any drawing made a screen. A form takes the whole screen with its FullScreen property instead.
|
||||||
|
|
||||||
.h2 Text: CLS, LOCATE, COLOR, CSRLIN, POS, SCREEN( )
|
.h2 Text: CLS, LOCATE, COLOR, CSRLIN, POS, SCREEN( )
|
||||||
|
|
||||||
.code
|
.code
|
||||||
|
|
@ -2516,7 +2525,7 @@ Name "draft.txt" As "final.txt"
|
||||||
|
|
||||||
.h2 FILECOPY
|
.h2 FILECOPY
|
||||||
|
|
||||||
Copies a file. If the destination already exists it is overwritten.
|
Copies a file. If the destination already exists it is overwritten. While a big file copies, the rest of DVX and the program's own events go on, as with DoEvents (in a control's code they wait until the copy ends).
|
||||||
|
|
||||||
.code
|
.code
|
||||||
FILECOPY source$, destination$
|
FILECOPY source$, destination$
|
||||||
|
|
@ -2584,7 +2593,7 @@ SetAttr "secret.dat", vbHidden + vbReadOnly
|
||||||
|
|
||||||
.h2 CURDIR$ / DIR$
|
.h2 CURDIR$ / DIR$
|
||||||
|
|
||||||
CURDIR$ returns the current directory. DIR$ returns the first file matching a pattern; subsequent calls with no arguments return additional matches (or "" when done). DIR$ returns only files: never ".", "..", or subdirectories. DIR$("C:\*.*") lists the root of C: (DIR$("C:*.*") the drive's current folder).
|
CURDIR$ returns the current directory. DIR$ returns the first file matching a pattern; subsequent calls with no arguments return additional matches (or "" when done). DIR$ returns only files: never ".", "..", or subdirectories. The pattern uses DOS's wildcards, as the file dialog does: * stands for any characters and ? for one, case does not matter, and *.* is every file, with or without an extension. DIR$("C:\*.*") lists the root of C: (DIR$("C:*.*") the drive's current folder). Looking through a big folder for a rare match lets the rest of DVX and the program's own events go on, as with DoEvents; if one of those events starts a new DIR$ listing, the call returns "" (in a control's code nothing else runs meanwhile).
|
||||||
|
|
||||||
.code
|
.code
|
||||||
cwd$ = CURDIR$
|
cwd$ = CURDIR$
|
||||||
|
|
@ -2608,7 +2617,9 @@ bytes = FILELEN(filename$)
|
||||||
.toc 1 Built-in Functions
|
.toc 1 Built-in Functions
|
||||||
.toc 2 String Functions
|
.toc 2 String Functions
|
||||||
.index ASC
|
.index ASC
|
||||||
|
.index ASCW
|
||||||
.index CHR$
|
.index CHR$
|
||||||
|
.index CHRW$
|
||||||
.index FORMAT$
|
.index FORMAT$
|
||||||
.index HEX$
|
.index HEX$
|
||||||
.index INSTR
|
.index INSTR
|
||||||
|
|
@ -2640,7 +2651,9 @@ bytes = FILELEN(filename$)
|
||||||
Function Returns Description
|
Function Returns Description
|
||||||
-------- ------- -----------
|
-------- ------- -----------
|
||||||
ASC(s$) Integer ASCII code of the first character of s$ (error 5 for an empty string)
|
ASC(s$) Integer ASCII code of the first character of s$ (error 5 for an empty string)
|
||||||
|
ASCW(s$) Long Unicode code point of the first character of s$ (error 5 for an empty string)
|
||||||
CHR$(n) String Character with ASCII code n
|
CHR$(n) String Character with ASCII code n
|
||||||
|
CHRW$(n) String Character for Unicode code point n ("?" when DVX has no such character; error 5 outside 0 to &H10FFFF)
|
||||||
FILTER(arr, match$ [, include [, compare]]) String array The elements of arr that contain match$ (or, with include False, that do not)
|
FILTER(arr, match$ [, include [, compare]]) String array The elements of arr that contain match$ (or, with include False, that do not)
|
||||||
FORMAT$(value [, fmt$]) String Formats a number or a date using a format string
|
FORMAT$(value [, fmt$]) String Formats a number or a date using a format string
|
||||||
HEX$(n) String Hexadecimal representation of n (uppercase, no leading &H)
|
HEX$(n) String Hexadecimal representation of n (uppercase, no leading &H)
|
||||||
|
|
@ -2840,7 +2853,7 @@ If length is omitted, MID$ replaces up to LEN(replacement$) characters; a length
|
||||||
SIN(n) Double Sine of n (n in radians)
|
SIN(n) Double Sine of n (n in radians)
|
||||||
SQR(n) Double Square root of n
|
SQR(n) Double Square root of n
|
||||||
TAN(n) Double Tangent of n (n in radians)
|
TAN(n) Double Tangent of n (n in radians)
|
||||||
TIMER Double Seconds since midnight
|
TIMER Double Seconds since midnight, to a fraction of a millisecond (it keeps rising between the clock's 55 ms ticks)
|
||||||
.endtable
|
.endtable
|
||||||
|
|
||||||
RND follows QBasic: RND or RND(n) with n > 0 returns the next number in the sequence, RND(0) repeats the last number, and RND(n) with n < 0 reseeds the generator from n (the same n always gives the same number). The result is a Double, 0 <= RND < 1.
|
RND follows QBasic: RND or RND(n) with n > 0 returns the next number in the sequence, RND(0) repeats the last number, and RND(n) with n < 0 reseeds the generator from n (the same n always gives the same number). The result is a Double, 0 <= RND < 1.
|
||||||
|
|
@ -3091,6 +3104,7 @@ File sizes and positions are Longs, so they stop at 2 GB (2,147,483,647 bytes).
|
||||||
.index IIF
|
.index IIF
|
||||||
.index CHOOSE
|
.index CHOOSE
|
||||||
.index LOADPICTURE
|
.index LOADPICTURE
|
||||||
|
.index LOADPICTUREDATA
|
||||||
|
|
||||||
.h1 Miscellaneous Functions
|
.h1 Miscellaneous Functions
|
||||||
|
|
||||||
|
|
@ -3099,7 +3113,7 @@ File sizes and positions are Longs, so they stop at 2 GB (2,147,483,647 bytes).
|
||||||
-------- ------- -----------
|
-------- ------- -----------
|
||||||
DATE$ String Current date as "MM-DD-YYYY"
|
DATE$ String Current date as "MM-DD-YYYY"
|
||||||
TIME$ String Current time as "HH:MM:SS" (24-hour)
|
TIME$ String Current time as "HH:MM:SS" (24-hour)
|
||||||
TIMER Double Seconds since midnight
|
TIMER Double Seconds since midnight, to a fraction of a millisecond
|
||||||
ENVIRON$(name$) String Value of the environment variable; empty string if not set
|
ENVIRON$(name$) String Value of the environment variable; empty string if not set
|
||||||
COMMAND$ String The arguments the program was started with; empty string if none
|
COMMAND$ String The arguments the program was started with; empty string if none
|
||||||
COMMAND$(n) String The nth argument (1 = first) without its quotes; empty string when there are fewer; COMMAND$(0) is the same as COMMAND$
|
COMMAND$(n) String The nth argument (1 = first) without its quotes; empty string when there are fewer; COMMAND$(0) is the same as COMMAND$
|
||||||
|
|
@ -3123,7 +3137,7 @@ Print Choose(Weekday(Date), "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat")
|
||||||
|
|
||||||
.h2 LOADPICTURE
|
.h2 LOADPICTURE
|
||||||
|
|
||||||
LOADPICTURE(file$) reads a picture file (BMP, PNG, JPEG, or GIF) -- or the icon of a DVX program, widget, or library file (.app, .wgt, .lib) -- and returns a Picture object (TypeName "Picture") that can be given to a picture property: a control's DragIcon, the Picture of an Image or ImageButton. Use SET, or a plain assignment, as with a file name; file names still work too. LOADPICTURE() or LOADPICTURE("") gives an empty picture, which clears the property. A missing file raises error 53 (File not found), a file that is not a picture error 481 (Invalid picture), so the check happens where the picture is loaded. Loading the same file again gives the same Picture object. Reading a picture property back gives a Picture object too (TypeName "Picture"), as in Visual Basic; used as a value (printed, joined to text, compared) it stands for its file name, so code that treats the property as a file name keeps working. The name of a program, widget, or library file works wherever a picture file's name does (a Picture property, DragIcon, PaintPicture, a Toolbar's AddButton) and shows its icon; such a file without an icon is not a picture (error 481).
|
LOADPICTURE(file$) reads a picture file (BMP, PNG, JPEG, or GIF) -- or the icon of a DVX program, widget, or library file (.app, .wgt, .lib) -- and returns a Picture object (TypeName "Picture") that can be given to a picture property: a control's DragIcon, the Picture of an Image or ImageButton. Use SET, or a plain assignment, as with a file name; file names still work too. LOADPICTURE() or LOADPICTURE("") gives an empty picture, which clears the property. A missing file raises error 53 (File not found), a file that is not a picture error 481 (Invalid picture), so the check happens where the picture is loaded. The check reads the picture's header only (the control that shows it reads the rest): a picture file damaged after its header shows nothing. Loading the same file again gives the same Picture object. Reading a picture property back gives a Picture object too (TypeName "Picture"), as in Visual Basic; used as a value (printed, joined to text, compared) it stands for its file name, so code that treats the property as a file name keeps working. The name of a program, widget, or library file works wherever a picture file's name does (a Picture property, DragIcon, PaintPicture, a Toolbar's AddButton) and shows its icon; such a file without an icon is not a picture (error 481).
|
||||||
|
|
||||||
The optional size and colordepth arguments are Visual Basic's: size is vbLPSmall (0), vbLPLarge (1), vbLPSmallShell (2), vbLPLargeShell (3), or vbLPCustom (4, with x and y giving the size), colordepth vbLPDefault (0), vbLPMonochrome (1), vbLPVGAColor (2), or vbLPColor (3). They choose among the images of an icon file; every DVX picture is a single bitmap, so, as for a bitmap in Visual Basic, they are checked and otherwise make no difference. A value outside these, or a negative x or y, raises error 5.
|
The optional size and colordepth arguments are Visual Basic's: size is vbLPSmall (0), vbLPLarge (1), vbLPSmallShell (2), vbLPLargeShell (3), or vbLPCustom (4, with x and y giving the size), colordepth vbLPDefault (0), vbLPMonochrome (1), vbLPVGAColor (2), or vbLPColor (3). They choose among the images of an icon file; every DVX picture is a single bitmap, so, as for a bitmap in Visual Basic, they are checked and otherwise make no difference. A value outside these, or a negative x or y, raises error 5.
|
||||||
|
|
||||||
|
|
@ -3149,6 +3163,18 @@ Set pic = LoadPicture("logo.bmp")
|
||||||
Print Me.ScaleX(pic.Width, vbHimetric, vbPixels); "x"; Me.ScaleY(pic.Height, vbHimetric, vbPixels)
|
Print Me.ScaleX(pic.Width, vbHimetric, vbPixels); "x"; Me.ScaleY(pic.Height, vbHimetric, vbPixels)
|
||||||
.endcode
|
.endcode
|
||||||
|
|
||||||
|
.h2 LOADPICTUREDATA
|
||||||
|
|
||||||
|
LOADPICTUREDATA(bytes$) does what LOADPICTURE does for a picture held in a string rather than in a file: the bytes of a BMP, PNG, JPEG, or GIF file, such as HttpBody$ gives for a picture downloaded from the web, or ResGetData$ for a picture resource of a DVX program, widget, or library file (any of its resources, not only its icon). The Picture object it returns goes wherever a LOADPICTURE picture goes: the Picture of an Image, an ImageButton, or a PictureBox, a control's DragIcon, a form's Icon, and PaintPicture; reading such a property back gives the same object. Its Width, Height, and Type are a loaded picture's; used as a value it stands for "", since it has no file name. Bytes that are not a picture raise error 481 (Invalid picture); LOADPICTUREDATA("") gives an empty picture. Giving one to a control that shows no picture (a Label's Picture, for example) stops the program with an error.
|
||||||
|
|
||||||
|
A Picture object made this way keeps its bytes until the program ends, and the same bytes give the same object, so loading a picture again costs nothing. To show many different pictures one after another (a slide show, pictures from the web), use the SetPictureData method of the Image, ImageButton, or PictureBox instead: Image1.SetPictureData bytes$ shows the picture without keeping it, and returns True, or False when the bytes are not a picture.
|
||||||
|
|
||||||
|
.code
|
||||||
|
Image1.Picture = LoadPictureData(HttpBody$(req))
|
||||||
|
Set Label1.DragIcon = LoadPictureData(ResGetData$("mygame.app", "card")) ' resource.bas in the project
|
||||||
|
If Not Picture1.SetPictureData(HttpBody$(req)) Then MsgBox "That is not a picture."
|
||||||
|
.endcode
|
||||||
|
|
||||||
.h2 COMMAND$
|
.h2 COMMAND$
|
||||||
|
|
||||||
COMMAND$ returns the program's arguments exactly as they were given, as in Visual Basic (QBasic turned them into capitals; DVX keeps them as they are). Arguments are separated by blanks; an argument with blanks in it is written in double quotes. COMMAND$(n) picks out one argument with its quotes removed, so a program that opens the document it was started with only needs:
|
COMMAND$ returns the program's arguments exactly as they were given, as in Visual Basic (QBasic turned them into capitals; DVX keeps them as they are). Arguments are separated by blanks; an argument with blanks in it is written in double quotes. COMMAND$(n) picks out one argument with its quotes removed, so a program that opens the document it was started with only needs:
|
||||||
|
|
@ -3996,6 +4022,9 @@ Through a variable declared As Collection the members are checked when the progr
|
||||||
.index SQLFieldCount
|
.index SQLFieldCount
|
||||||
.index SQLFreeResult
|
.index SQLFreeResult
|
||||||
.index SQLError$
|
.index SQLError$
|
||||||
|
.index SQLEscape$
|
||||||
|
.index SQLFieldName$
|
||||||
|
.index SQLFieldText$
|
||||||
.index SQLite
|
.index SQLite
|
||||||
|
|
||||||
.h1 SQL Functions
|
.h1 SQL Functions
|
||||||
|
|
@ -4044,6 +4073,24 @@ ok = SQLExec(db, "DELETE FROM users WHERE id = 5")
|
||||||
|
|
||||||
Put text values in single quotes, as in the examples. Text in double quotes is read as a column name, so "Scott" in place of 'Scott' gives the error "no such column: Scott".
|
Put text values in single quotes, as in the examples. Text in double quotes is read as a column name, so "Scott" in place of 'Scott' gives the error "no such column: Scott".
|
||||||
|
|
||||||
|
.h3 SQLEscape$
|
||||||
|
|
||||||
|
Returns text with every single quote doubled, the way SQL writes a quote inside a quoted value. Use it on text the user typed (or read from a file) before you put it between quotes in a statement: a name such as O'Brien would otherwise end the value early and break the statement.
|
||||||
|
|
||||||
|
.code
|
||||||
|
SQLExec db, "INSERT INTO users (name) VALUES ('" & SQLEscape$(name$) & "')"
|
||||||
|
.endcode
|
||||||
|
|
||||||
|
SQLExec runs every statement in sql$, one after another, before it returns.
|
||||||
|
|
||||||
|
.h3 Long Statements
|
||||||
|
|
||||||
|
A statement can take a while: one that changes many rows, CREATE INDEX on a big table, or the first SQLNext of a query that sorts or groups, which reads every row first. While a statement runs, DVX and the other programs keep going, and your program's events run as they do during DoEvents. Ending the program (End, or closing its last form) stops the statement, and what it changed is undone.
|
||||||
|
|
||||||
|
An event that runs meanwhile cannot use the database the statement is running on: SQLExec, SQLQuery, and SQLNext return False or 0, and SQLError$ says "A statement is still running on this database". Its field functions return "" or 0. SQLFreeResult and SQLClose wait until the statement ends. Other databases can be used as always.
|
||||||
|
|
||||||
|
Code in a control file (a control written in BASIC) does not wait like this: its statements run to the end.
|
||||||
|
|
||||||
.h3 SQLAffected
|
.h3 SQLAffected
|
||||||
|
|
||||||
Returns the number of rows affected by the last INSERT, UPDATE, or DELETE.
|
Returns the number of rows affected by the last INSERT, UPDATE, or DELETE.
|
||||||
|
|
@ -4920,15 +4967,21 @@ Colors in the form RGB makes (&HBBGGRR, see Color Functions), as in Visual Basic
|
||||||
.index CommHandshake
|
.index CommHandshake
|
||||||
.index CommRecvEncrypted
|
.index CommRecvEncrypted
|
||||||
.index CommSetPlain
|
.index CommSetPlain
|
||||||
|
.index CommOpenTcp
|
||||||
.index print.bas
|
.index print.bas
|
||||||
.index PrintTextDialog
|
.index PrintTextDialog
|
||||||
.index PrintPictureDialog
|
.index PrintPictureDialog
|
||||||
.index PrintPageSetup
|
.index PrintPageSetup
|
||||||
.index net.bas
|
.index net.bas
|
||||||
.index TcpConnect
|
.index TcpConnect
|
||||||
|
.index TcpConnectStart
|
||||||
.index TcpRecv$
|
.index TcpRecv$
|
||||||
|
.index TcpPeek$
|
||||||
|
.index NetState
|
||||||
|
.index TcpSendSome
|
||||||
.index UdpSend
|
.index UdpSend
|
||||||
.index HostLookup$
|
.index HostLookup$
|
||||||
|
.index HostLookupStart
|
||||||
.index game.bas
|
.index game.bas
|
||||||
.index GameKeyDown
|
.index GameKeyDown
|
||||||
.index GameKeyClear
|
.index GameKeyClear
|
||||||
|
|
@ -5138,10 +5191,17 @@ Colors in the form RGB makes (&HBBGGRR, see Color Functions), as in Visual Basic
|
||||||
.index security.bas
|
.index security.bas
|
||||||
.index SecRandom$
|
.index SecRandom$
|
||||||
.index SecDhCreate
|
.index SecDhCreate
|
||||||
|
.index SecDhStep
|
||||||
.index SecCipherCreate
|
.index SecCipherCreate
|
||||||
.index SecSha256$
|
.index SecSha256$
|
||||||
.index SecHmac$
|
.index SecHmac$
|
||||||
|
.index SecX25519$
|
||||||
|
.index SecAeadLock$
|
||||||
|
.index SecAeadUnlock$
|
||||||
|
.index SecEd25519Sign$
|
||||||
|
.index SecEd25519Check
|
||||||
.index Encryption
|
.index Encryption
|
||||||
|
.index Signatures
|
||||||
.index SerSet
|
.index SerSet
|
||||||
.index SerLastError
|
.index SerLastError
|
||||||
.index CommHandshakeStep
|
.index CommHandshakeStep
|
||||||
|
|
@ -5175,6 +5235,9 @@ Colors in the form RGB makes (&HBBGGRR, see Color Functions), as in Visual Basic
|
||||||
.index ShellRunningActivate
|
.index ShellRunningActivate
|
||||||
.index ShellRunningClose
|
.index ShellRunningClose
|
||||||
.index ShellRunningKill
|
.index ShellRunningKill
|
||||||
|
.index ShellRunningPriority
|
||||||
|
.index ShellRunDosWait
|
||||||
|
.index ShellRunningSetPriority
|
||||||
.index ShellAppIsHung
|
.index ShellAppIsHung
|
||||||
.index ShellAppMemory
|
.index ShellAppMemory
|
||||||
.index ShellAppId
|
.index ShellAppId
|
||||||
|
|
@ -5244,9 +5307,22 @@ Colors in the form RGB makes (&HBBGGRR, see Color Functions), as in Visual Basic
|
||||||
.index PkgInstallStart
|
.index PkgInstallStart
|
||||||
.index PkgRunFor
|
.index PkgRunFor
|
||||||
.index PkgRemoveStart
|
.index PkgRemoveStart
|
||||||
.index PkgBuild
|
.index PkgBuildStart
|
||||||
.index PkgSign
|
.index PkgSignStart
|
||||||
.index PkgKeygen
|
.index PkgKeygen
|
||||||
|
.index PkgOpenIn
|
||||||
|
.index PkgKeysRows$
|
||||||
|
.index PkgUntrustKey
|
||||||
|
.index PkgVerifyStart
|
||||||
|
.index PkgSignData$
|
||||||
|
.index PkgCheckData
|
||||||
|
.index PkgZipListStart
|
||||||
|
.index PkgZipExtractStart
|
||||||
|
.index PkgZipCreateStart
|
||||||
|
.index PkgZipRows$
|
||||||
|
.index PkgVersionCompare
|
||||||
|
.index PkgNetOpenIn
|
||||||
|
.index ZIP Files
|
||||||
.index json.bas
|
.index json.bas
|
||||||
.index http.bas
|
.index http.bas
|
||||||
.index pkgnet.bas
|
.index pkgnet.bas
|
||||||
|
|
@ -5254,6 +5330,7 @@ Colors in the form RGB makes (&HBBGGRR, see Color Functions), as in Visual Basic
|
||||||
.index Web requests
|
.index Web requests
|
||||||
.index HttpOpen
|
.index HttpOpen
|
||||||
.index HttpRunFor
|
.index HttpRunFor
|
||||||
|
.index HttpSetBodyFile
|
||||||
.index JsonParse
|
.index JsonParse
|
||||||
|
|
||||||
.h1 Include Libraries
|
.h1 Include Libraries
|
||||||
|
|
@ -5265,7 +5342,7 @@ DVX ships a set of BASIC include files (.bas) in the sdk/include/basic directory
|
||||||
---- --------
|
---- --------
|
||||||
commdlg.bas File Open/Save dialogs (DlgFileOpenIn$ and DlgFileSaveIn$ start in a folder you name), the folder dialog (DlgFolder$, for a Browse button), input dialogs, choice dialogs, save-prompt dialogs, the color dialog, the font dialog (DlgFont), message boxes with your own buttons (DlgButtons)
|
commdlg.bas File Open/Save dialogs (DlgFileOpenIn$ and DlgFileSaveIn$ start in a folder you name), the folder dialog (DlgFolder$, for a Browse button), input dialogs, choice dialogs, save-prompt dialogs, the color dialog, the font dialog (DlgFont), message boxes with your own buttons (DlgButtons)
|
||||||
sql.bas SQLite database access (SQLOpen, SQLQuery, SQLNext, SQLField$, etc.)
|
sql.bas SQLite database access (SQLOpen, SQLQuery, SQLNext, SQLField$, etc.)
|
||||||
comm.bas Serial-port I/O (raw and packet) and optional encrypted links; port settings read back and changed, why a call failed, breaks, ring, and line errors (SerOpen, SerSet, SerLastError, CommOpen, CommHandshakeStep, ...)
|
comm.bas Serial-port I/O (raw and packet) and optional encrypted links; port settings read back and changed, why a call failed, breaks, ring, and line errors (SerOpen, SerSet, SerLastError, CommOpen, CommOpenTcp, CommHandshakeStep, ...)
|
||||||
conn.bas Connections the way Terminal makes them: a serial port, a modem that dials, the secure link to the DVX host proxy, or Telnet, shown on a Terminal control, with capture, answerback, and file transfers (ConnCreate, ConnOpen, ConnPoll, ConnAttach, ConnSendFiles, ...)
|
conn.bas Connections the way Terminal makes them: a serial port, a modem that dials, the secure link to the DVX host proxy, or Telnet, shown on a Terminal control, with capture, answerback, and file transfers (ConnCreate, ConnOpen, ConnPoll, ConnAttach, ConnSendFiles, ...)
|
||||||
xfer.bas File transfers with XMODEM, XMODEM-1K, YMODEM, and ZMODEM, on a serial port or a connection, with their progress and questions (XferSendFile, XferStatus, XferBytes, XferAnswer, ...)
|
xfer.bas File transfers with XMODEM, XMODEM-1K, YMODEM, and ZMODEM, on a serial port or a connection, with their progress and questions (XferSendFile, XferStatus, XferBytes, XferAnswer, ...)
|
||||||
prefs.bas Settings files (INI): load one, read and change its settings, list the keys of a section, save it; DVX's own settings file, the screen mode it starts in, the names its choices are stored as and its default choices, and choices stored by names of your own (PrefsLoad, PrefsGet$, PrefsSet, PrefsKeyName$, PrefsSaveAs, PrefsDvxIni$, PrefsVideoWidth, PrefsChoiceLabel$, PrefsChoiceDefault, PrefsGetChoiceFrom, ...)
|
prefs.bas Settings files (INI): load one, read and change its settings, list the keys of a section, save it; DVX's own settings file, the screen mode it starts in, the names its choices are stored as and its default choices, and choices stored by names of your own (PrefsLoad, PrefsGet$, PrefsSet, PrefsKeyName$, PrefsSaveAs, PrefsDvxIni$, PrefsVideoWidth, PrefsChoiceLabel$, PrefsChoiceDefault, PrefsGetChoiceFrom, ...)
|
||||||
|
|
@ -5275,7 +5352,7 @@ DVX ships a set of BASIC include files (.bas) in the sdk/include/basic directory
|
||||||
net.bas Network connections: TCP (TcpConnect, TcpListen, TcpSend, TcpRecv$, TcpPeer$, ...), UDP messages and name lookups, any bytes; the network settings and status (NetConfigLoad, NetConfigSet, NetConfigStore, NetRestart, NetStatus$, ...)
|
net.bas Network connections: TCP (TcpConnect, TcpListen, TcpSend, TcpRecv$, TcpPeer$, ...), UDP messages and name lookups, any bytes; the network settings and status (NetConfigLoad, NetConfigSet, NetConfigStore, NetRestart, NetStatus$, ...)
|
||||||
print.bas Printing: text with the page setup and pictures, with or without the Print dialog; a form; the Page Setup dialog; the printer and page settings; the printer ports (PrintModelLabel$, PrintSettingsStore, PrintPageSetting, PrintChoose, PrintForm, PrintPortStatus, ...)
|
print.bas Printing: text with the page setup and pictures, with or without the Print dialog; a form; the Page Setup dialog; the printer and page settings; the printer ports (PrintModelLabel$, PrintSettingsStore, PrintPageSetting, PrintChoose, PrintForm, PrintPortStatus, ...)
|
||||||
listhelp.bas Lists and icon views you draw yourself (a UserControl, a PictureBox) that behave like ListBox and Program Manager: selection with Shift and Ctrl, the arrow keys in a list or a grid, Ctrl+A, Space, type-ahead, where a dragged item lands, and Program Manager's icon view rules -- its cells, the grid with a scroll bar, names on two lines, dragging and dropping (ListSelCreate, ListSelClick, ListSelNavigate, ListTypeAhead, ListIconMetric, ListIconLabel$, ...)
|
listhelp.bas Lists and icon views you draw yourself (a UserControl, a PictureBox) that behave like ListBox and Program Manager: selection with Shift and Ctrl, the arrow keys in a list or a grid, Ctrl+A, Space, type-ahead, where a dragged item lands, and Program Manager's icon view rules -- its cells, the grid with a scroll bar, names on two lines, dragging and dropping (ListSelCreate, ListSelClick, ListSelNavigate, ListTypeAhead, ListIconMetric, ListIconLabel$, ...)
|
||||||
security.bas Security: random bytes for keys, a key exchange, a cipher, SHA-256 hashes, HMAC signatures, key making, and a safe compare (SecRandom$, SecDhCreate, SecCipherCrypt$, SecSha256$, SecHmac$, SecHkdf$, ...)
|
security.bas Security: random bytes for keys, key exchanges, ciphers, sealing, SHA-256 hashes, HMAC and public-key signatures, key making, and a safe compare (SecRandom$, SecX25519$, SecAeadLock$, SecEd25519Sign$, SecSha256$, SecHkdf$, ...)
|
||||||
game.bas Games: whether a key is being held down right now, forgetting held keys, frames FRAMEWAIT skipped, waiting for the screen's retrace (GameKeyDown, GameKeyClear, FrameMissed, GameWaitRetrace)
|
game.bas Games: whether a key is being held down right now, forgetting held keys, frames FRAMEWAIT skipped, waiting for the screen's retrace (GameKeyDown, GameKeyClear, FrameMissed, GameWaitRetrace)
|
||||||
joystick.bas Joysticks: how many there are, their names, where each stick points, and which buttons are pressed (JoyCount, JoyX, JoyY, JoyButton, ...); setting them up: calibration, dead zone, game port mode, keyboard joystick keys (JoySetCalibration, JoyCalibration, JoyRescan, JoyKeyIndex, JoySettingsStore, ...)
|
joystick.bas Joysticks: how many there are, their names, where each stick points, and which buttons are pressed (JoyCount, JoyX, JoyY, JoyButton, ...); setting them up: calibration, dead zone, game port mode, keyboard joystick keys (JoySetCalibration, JoyCalibration, JoyRescan, JoyKeyIndex, JoySettingsStore, ...)
|
||||||
sound.bas Sound: play WAV and VOC files, tones and noise, set the volume (PlayWave, PlayTone, StopWave, SoundInfo$, ...); sounds loaded once and played with volume, pan, and pitch, sound made by the program as it goes, recording from a microphone or line input (RecordStart, RecordStop, RecordLevel, ...), the FM synthesizer chip, a wavetable card's voices and memory (SoundLoad, SoundPlay, SoundSetPitch, SoundCreate, SoundStreamOpen, SoundStreamWrite, FmAlloc, FmWrite, WtAlloc, WtStart, ...); DVX's own event sounds (SoundPlayEvent), master volume, sound driver, and MIDI settings (SoundEventFile$, SoundSetMasterVolume, SoundSettingsStore, ...)
|
sound.bas Sound: play WAV and VOC files, tones and noise, set the volume (PlayWave, PlayTone, StopWave, SoundInfo$, ...); sounds loaded once and played with volume, pan, and pitch, sound made by the program as it goes, recording from a microphone or line input (RecordStart, RecordStop, RecordLevel, ...), the FM synthesizer chip, a wavetable card's voices and memory (SoundLoad, SoundPlay, SoundSetPitch, SoundCreate, SoundStreamOpen, SoundStreamWrite, FmAlloc, FmWrite, WtAlloc, WtStart, ...); DVX's own event sounds (SoundPlayEvent), master volume, sound driver, and MIDI settings (SoundEventFile$, SoundSetMasterVolume, SoundSettingsStore, ...)
|
||||||
|
|
@ -5288,8 +5365,8 @@ DVX ships a set of BASIC include files (.bas) in the sdk/include/basic directory
|
||||||
compiler.bas The BASIC compiler: compile a project or files you give it, find compile errors in their files, list a program's procedures and the language's keywords, build an .app or a control file; put a project's UserControls on a form designer's toolbox; read source text as the compiler does (tokens, code or string or comment, renaming a control in code, WithEvents variables, class Events), F1 help topics; open, change, and save project files and UserControl headers (CompSourceFromProject, CompSourceAdd, CompCompile, CompError$, CompMapFile$, CompProcList$, CompBuildApp, CompDesignControls, CompTokens$, CompRenameInCode$, CompHelpTopic$, ProjOpen, ProjSave, ProjCtlSetHeader$, ...)
|
compiler.bas The BASIC compiler: compile a project or files you give it, find compile errors in their files, list a program's procedures and the language's keywords, build an .app or a control file; put a project's UserControls on a form designer's toolbox; read source text as the compiler does (tokens, code or string or comment, renaming a control in code, WithEvents variables, class Events), F1 help topics; open, change, and save project files and UserControl headers (CompSourceFromProject, CompSourceAdd, CompCompile, CompError$, CompMapFile$, CompProcList$, CompBuildApp, CompDesignControls, CompTokens$, CompRenameInCode$, CompHelpTopic$, ProjOpen, ProjSave, ProjCtlSetHeader$, ...)
|
||||||
debug.bas Run a compiled program inside yours and debug it: breakpoints, stepping, its variables, expressions, Immediate-window lines, its PRINT output (RunCreate, RunStart, RunPoll, DbgSetBreakpoints, DbgStepOver, DbgLocals$, DbgEval$, DbgExec$, ...)
|
debug.bas Run a compiled program inside yours and debug it: breakpoints, stepping, its variables, expressions, Immediate-window lines, its PRINT output (RunCreate, RunStart, RunPoll, DbgSetBreakpoints, DbgStepOver, DbgLocals$, DbgEval$, DbgExec$, ...)
|
||||||
designer.bas Form designers: the control types, whether each holds other controls and can be placed, the names of new controls, default events, toolbox tooltips and icons, and each type's properties (with their types and value names), methods, and events (with each event's parameters); the names in .frm and class module text; .frm files as data to read, edit, and write, controls copied and pasted as text, and a loaded form written back as .frm text (CtlTypeCount, CtlTypeName$, CtlTypeFlags, CtlPropCount, CtlPropName$, CtlPropType, CtlEventList$, CtlEventParams$, CtlTypeEventParams$, FrmFormName$, FrmControlNames$, ClsClassName$, FrmOpen, FrmText$, FrmCtlAdd, FrmCtlText$, FrmCtlPaste, FrmMenuAdd, FormText$, ...)
|
designer.bas Form designers: the control types, whether each holds other controls and can be placed, the names of new controls, default events, toolbox tooltips and icons, and each type's properties (with their types and value names), methods, and events (with each event's parameters); the names in .frm and class module text; .frm files as data to read, edit, and write, controls copied and pasted as text, and a loaded form written back as .frm text (CtlTypeCount, CtlTypeName$, CtlTypeFlags, CtlPropCount, CtlPropName$, CtlPropType, CtlEventList$, CtlEventParams$, CtlTypeEventParams$, FrmFormName$, FrmControlNames$, ClsClassName$, FrmOpen, FrmText$, FrmCtlAdd, FrmCtlText$, FrmCtlPaste, FrmMenuAdd, FormText$, ...)
|
||||||
shell.bas DVX itself, everything Program Manager and Task Manager do: start programs (DVX's and DOS programs) and open documents, command lines (split into their parts) and the Run dialog, the installed programs and their names, file types, the running programs (find one, bring it to the front, ask it to close, or end it), this program's priority, keys that work in every program, Task Manager, system information, Exit DVX, the desktop and the program started after it, the DVX log; Program Manager's program groups with their items, shortcut keys, and icons (ShellRun, ShellRunProgram, ShellRunDos, ShellProgramScan, ShellAssocCount, ShellRunningId, ShellRunningActivate, ShellRunningClose, ShellArg$, ShellSetPriority, HotkeyAdd, ShellExitDvx, ShellGroupsLoad, ShellItemGet$, ShellItemIcon$, ...)
|
shell.bas DVX itself, everything Program Manager and Task Manager do: start programs (DVX's and DOS programs) and open documents, command lines (split into their parts) and the Run dialog, the installed programs and their names, file types, the running programs (find one, bring it to the front, ask it to close, or end it, and their priority), this program's priority, keys that work in every program, Task Manager, system information, Exit DVX, the desktop and the program started after it, the DVX log; Program Manager's program groups with their items, shortcut keys, and icons (ShellRun, ShellRunProgram, ShellRunDos, ShellProgramScan, ShellAssocCount, ShellRunningId, ShellRunningActivate, ShellRunningClose, ShellArg$, ShellSetPriority, HotkeyAdd, ShellExitDvx, ShellGroupsLoad, ShellItemGet$, ShellItemIcon$, ...)
|
||||||
pkg.bas Add-on packages: the packages installed in DVX and their details, files, and needs; reading .DPK files and planning what installing them takes; installing and removing as a job with progress; trusting a publisher's key; and making packages, signing them, and making keys (PkgOpen, PkgInstalledRows$, PkgOfferFile, PkgPlan, PkgInstallStart, PkgRunFor, PkgRemoveStart, PkgBuild, PkgSign, PkgKeygen, ...)
|
pkg.bas Add-on packages: the packages installed in DVX and their details, files, and needs; reading .DPK files, installing, removing, making, and signing packages as jobs with progress, and planning what installing them takes; trusting a publisher's key; and making keys (PkgOpen, PkgInstalledRows$, PkgOfferFileStart, PkgPlan, PkgInstallStart, PkgRunFor, PkgRemoveStart, PkgBuildStart, PkgSignStart, PkgKeygen, ...)
|
||||||
json.bas JSON: read a document (a web service's reply, a settings file) and find values in it by path such as packages[3].version, and quote text for JSON of your own (JsonParse, JsonText$, JsonNumber, JsonCount, JsonKey$, JsonQuote$, ...)
|
json.bas JSON: read a document (a web service's reply, a settings file) and find values in it by path such as packages[3].version, and quote text for JSON of your own (JsonParse, JsonText$, JsonNumber, JsonCount, JsonKey$, JsonQuote$, ...)
|
||||||
http.bas Web requests: GET a page, a file, or a web service's answer, or POST to one, over the network a little at a time with progress, to memory or to a file (resuming one left part-way), following redirects (HttpOpen, HttpStart, HttpRunFor, HttpStatus, HttpBody$, HttpSaveTo, ...)
|
http.bas Web requests: GET a page, a file, or a web service's answer, or POST to one, over the network a little at a time with progress, to memory or to a file (resuming one left part-way), following redirects (HttpOpen, HttpStart, HttpRunFor, HttpStatus, HttpBody$, HttpSaveTo, ...)
|
||||||
pkgnet.bas A package server: what it offers, what it has that is newer than what is installed, and fetching packages with what they need, for pkg.bas to install (PkgNetOpen, PkgNetCatalogue, PkgNetUpdates, PkgNetFetch, PkgNetRunFor, PkgNetRows$, ...)
|
pkgnet.bas A package server: what it offers, what it has that is newer than what is installed, and fetching packages with what they need, for pkg.bas to install (PkgNetOpen, PkgNetCatalogue, PkgNetUpdates, PkgNetFetch, PkgNetRunFor, PkgNetRows$, ...)
|
||||||
|
|
@ -5386,7 +5463,7 @@ End If
|
||||||
|
|
||||||
.h2 Resources in Files
|
.h2 Resources in Files
|
||||||
|
|
||||||
With resource.bas in the project, a program reads and writes the named resources DVX keeps at the end of program, widget, and library files: icons, text, and any bytes. ResOpen, ResCount, ResName$, ResType, and ResSize list them; ResGetText$ and ResAddText read and write text; ResAddFile and ResExtract copy a resource from or to a file; ResRemove takes one out. ResGetData$(file, name) gives every byte of a resource as a String (CHR$(0) included), and ResAddData file, name, type, bytes adds or replaces a resource of any bytes (type RES_TYPE_BINARY, RES_TYPE_TEXT, RES_TYPE_ICON, or RES_TYPE_FONT, a .DFN font the program carries; see Fonts). RES_NAME_ICON ("icon32") and RES_NAME_WIDGET_ICON ("icon24") are the names DVX gives a program's and a widget's icon; a name is at most RES_NAME_MAX - 1 characters.
|
With resource.bas in the project, a program reads and writes the named resources DVX keeps at the end of program, widget, and library files: icons, text, and any bytes. ResOpen, ResCount, ResName$, ResType, and ResSize list them; ResGetText$ and ResAddText read and write text; ResAddFile and ResExtract copy a resource from or to a file; ResRemove takes one out. ResGetData$(file, name) gives every byte of a resource as a String (CHR$(0) included) -- LoadPictureData(ResGetData$(file, name)) shows a picture resource -- and ResAddData file, name, type, bytes adds or replaces a resource of any bytes (type RES_TYPE_BINARY, RES_TYPE_TEXT, RES_TYPE_ICON, or RES_TYPE_FONT, a .DFN font the program carries; see Fonts). RES_NAME_ICON ("icon32") and RES_NAME_WIDGET_ICON ("icon24") are the names DVX gives a program's and a widget's icon; a name is at most RES_NAME_MAX - 1 characters.
|
||||||
|
|
||||||
.code
|
.code
|
||||||
Dim settings As String
|
Dim settings As String
|
||||||
|
|
@ -5418,7 +5495,15 @@ End If
|
||||||
|
|
||||||
After the handshake, data that arrives unencrypted is ignored unless that channel is opened for it with CommSetPlain link, channel, True.
|
After the handshake, data that arrives unencrypted is ignored unless that channel is opened for it with CommSetPlain link, channel, True.
|
||||||
|
|
||||||
CommHandshake waits until the key exchange is done. CommHandshakeStep(link, passphrase) does one step of it and returns at once, so a program can keep working: call it from a Timer until it returns something other than COMM_PENDING -- COMM_OK when the link is ready, else a COMM_ERR_ code. CommLastError(link) gives the COMM_ code of the last CommHandshake, CommHandshakeStep, CommSend, CommSetPlain, or CommPoll, which tells a wrong passphrase (COMM_ERR_AUTH) from a line that does not answer (COMM_ERR_TIMEOUT). CommDropped(link) counts arriving packets thrown away because they were tampered with or came unencrypted where that is refused. Channels are numbered 0 to COMM_MAX_CHANNEL.
|
The same link runs over the network: with net.bas in the project too, CommOpenTcp(conn) opens it on a connection from TcpConnect or TcpAccept (both ends do), with every channel, the handshake, and encryption as on a COM port. The connection stays the program's: close it with TcpClose after CommClose.
|
||||||
|
|
||||||
|
.code
|
||||||
|
conn = TcpConnect("bbs.example.org", 2323)
|
||||||
|
link = CommOpenTcp(conn)
|
||||||
|
If CommHandshake(link, "shared secret") Then CommSend link, "hello", 5, True
|
||||||
|
.endcode
|
||||||
|
|
||||||
|
CommHandshake waits until the key exchange is done (up to 30 seconds), and CommSend waits while the link has as much in flight as it can; the program's events run while they wait, as they do in DoEvents. CommHandshakeStep(link, passphrase) does one step of it and returns at once, so a program can keep working: call it from a Timer until it returns something other than COMM_PENDING -- COMM_OK when the link is ready, else a COMM_ERR_ code. CommLastError(link) gives the COMM_ code of the last CommHandshake, CommHandshakeStep, CommSend, CommSetPlain, or CommPoll, which tells a wrong passphrase (COMM_ERR_AUTH) from a line that does not answer (COMM_ERR_TIMEOUT). CommDropped(link) counts arriving packets thrown away because they were tampered with or came unencrypted where that is refused. Channels are numbered 0 to COMM_MAX_CHANNEL.
|
||||||
|
|
||||||
.code
|
.code
|
||||||
Sub Timer1_Timer ()
|
Sub Timer1_Timer ()
|
||||||
|
|
@ -5550,7 +5635,7 @@ End Sub
|
||||||
|
|
||||||
.h2 Files and Folders
|
.h2 Files and Folders
|
||||||
|
|
||||||
With fileops.bas in the project, a program can show and change what is on the disks. FileOpsList(folder, filter) reads a folder once -- its folders and the files matching filter -- and returns a listing (a number; FileOpsListFree frees it). FileOpsListRows$ gives the whole listing as name, size, type, and date columns, ready for a ListView's SetRows, so even a folder of hundreds of files fills the list at once; FileOpsListSort re-sorts it by name, size, type, or date with folders first. FileOpsListName$, FileOpsListIsFolder, and FileOpsListSize read single entries, and FileOpsListFind finds one by name.
|
With fileops.bas in the project, a program can show and change what is on the disks. FileOpsList(folder, filter) reads a folder once -- its folders and the files matching filter -- and returns a listing (a number; FileOpsListFree frees it). FileOpsListRows$ gives the whole listing as name, size, type, and date columns, ready for a ListView's SetRows, so even a folder of hundreds of files fills the list at once; FileOpsListSort re-sorts it by name, size, type, or date with folders first. FileOpsListName$, FileOpsListIsFolder, and FileOpsListSize read single entries, and FileOpsListFind finds one by name. FileOpsListFolders(listing) makes a new listing of just the folders in one, sorted by name, without reading the disk again (handy for a folder tree next to a file list).
|
||||||
|
|
||||||
Paths: FileOpsPathNormalize$ (the full path of an existing folder, or ""), FileOpsPathParent$, FileOpsPathJoin$, FileOpsPathSame, FileOpsPathIsRoot, FileOpsPathIsAbsolute (True for a path that is complete by itself, such as "C:\DOCS", False for one that counts from the current folder), FileOpsPathIsInside(path, folder) (True when path is folder itself or anywhere inside it: check it before copying or moving a folder, so it is never copied into itself), FileOpsPathSep$ (the character between folder names, "\" on DOS), FileOpsPathNative$ (a path written with that character, for showing it: "C:/DOCS/A.TXT" becomes "C:\DOCS\A.TXT"), FileOpsBaseName$, and FileOpsExtension$. Every one of them takes / and \ alike and gives back \. FileOpsFilterMatch(itemName, filter) tells whether a name matches a filter the way the file dialogs match it: wildcards * and ?, several patterns separated by ; such as "*.bmp;*.png", case not mattering.
|
Paths: FileOpsPathNormalize$ (the full path of an existing folder, or ""), FileOpsPathParent$, FileOpsPathJoin$, FileOpsPathSame, FileOpsPathIsRoot, FileOpsPathIsAbsolute (True for a path that is complete by itself, such as "C:\DOCS", False for one that counts from the current folder), FileOpsPathIsInside(path, folder) (True when path is folder itself or anywhere inside it: check it before copying or moving a folder, so it is never copied into itself), FileOpsPathSep$ (the character between folder names, "\" on DOS), FileOpsPathNative$ (a path written with that character, for showing it: "C:/DOCS/A.TXT" becomes "C:\DOCS\A.TXT"), FileOpsBaseName$, and FileOpsExtension$. Every one of them takes / and \ alike and gives back \. FileOpsFilterMatch(itemName, filter) tells whether a name matches a filter the way the file dialogs match it: wildcards * and ?, several patterns separated by ; such as "*.bmp;*.png", case not mattering.
|
||||||
|
|
||||||
|
|
@ -5560,7 +5645,7 @@ Drives: FileOpsDriveList$ (the drive letters), FileOpsDriveType (FILEOPS_DRIVE_F
|
||||||
|
|
||||||
DVX's folders: FileOpsUserDir$() is the user folder (USER in the DVX folder, or the folder DVXUSER names), where everything DVX and its programs save goes; FileOpsUserPath$(name) is a file or folder in it, for example FileOpsUserPath$("DOWNLOAD"); FileOpsUserInTemp() is True when DVX runs from a disk it cannot write to and keeps the user folder in the TEMP folder. A program's own folder there is App.User. The fonts, sounds, themes, desktop pictures, MIDI instrument banks, and screen savers come in two folders each, DVX's own (and add-on packages') in SHARED and the user's in the user folder; the kinds are FILEOPS_KIND_FONTS, FILEOPS_KIND_SOUNDS, FILEOPS_KIND_THEMES, FILEOPS_KIND_WPAPER, FILEOPS_KIND_BANKS, and FILEOPS_KIND_SAVERS. An add-on's files are in a folder of its vendor's inside a kind's folder and are named VENDOR\NAME (for example ACME\SPACE.THM). FileOpsKindDir$(kind, user) is DVX's folder of a kind (user False) or the user's (True); FileOpsKindBrowseDir$(kind) is the one a file dialog starts in (the user's when it exists); FileOpsKindFind$(kind, fileName) finds a file by name, the user's first ("" when there is none); FileOpsKindName$(kind, path) is the name alone of a file in one of the two folders, the way settings store it ("" for any other path); FileOpsKindList(kind) lists both folders as one, sorted by name, a user's file hiding DVX's of the same name (a list for FileOpsListCount, FileOpsListName$, and the rest; FileOpsListFree when done). Programs read DVX's SHARED folder but never write in it.
|
DVX's folders: FileOpsUserDir$() is the user folder (USER in the DVX folder, or the folder DVXUSER names), where everything DVX and its programs save goes; FileOpsUserPath$(name) is a file or folder in it, for example FileOpsUserPath$("DOWNLOAD"); FileOpsUserInTemp() is True when DVX runs from a disk it cannot write to and keeps the user folder in the TEMP folder. A program's own folder there is App.User. The fonts, sounds, themes, desktop pictures, MIDI instrument banks, and screen savers come in two folders each, DVX's own (and add-on packages') in SHARED and the user's in the user folder; the kinds are FILEOPS_KIND_FONTS, FILEOPS_KIND_SOUNDS, FILEOPS_KIND_THEMES, FILEOPS_KIND_WPAPER, FILEOPS_KIND_BANKS, and FILEOPS_KIND_SAVERS. An add-on's files are in a folder of its vendor's inside a kind's folder and are named VENDOR\NAME (for example ACME\SPACE.THM). FileOpsKindDir$(kind, user) is DVX's folder of a kind (user False) or the user's (True); FileOpsKindBrowseDir$(kind) is the one a file dialog starts in (the user's when it exists); FileOpsKindFind$(kind, fileName) finds a file by name, the user's first ("" when there is none); FileOpsKindName$(kind, path) is the name alone of a file in one of the two folders, the way settings store it ("" for any other path); FileOpsKindList(kind) lists both folders as one, sorted by name, a user's file hiding DVX's of the same name (a list for FileOpsListCount, FileOpsListName$, and the rest; FileOpsListFree when done). Programs read DVX's SHARED folder but never write in it.
|
||||||
|
|
||||||
FileOpsCopyTree, FileOpsMoveTree, and FileOpsDeleteTree do a whole folder at once. For a long copy, move, or delete the program stays usable when it runs the work as a job, a little at a time from a Timer: FileOpsJobCreate, then FileOpsJobAddSource for each file or folder, then FileOpsJobRunFor from the timer until it returns FILEOPS_STEP_DONE or FILEOPS_STEP_CANCELLED. When it returns FILEOPS_STEP_ASK (a file already exists, a file is read-only) or FILEOPS_STEP_ERROR, show FileOpsJobMessage$ and pass the user's choice to FileOpsJobAnswer. FileOpsJobCurrent$, FileOpsJobFilesDone, FileOpsJobFilesTotal, FileOpsJobPercent, FileOpsJobBytesDone, and FileOpsJobBytesTotal show the progress, and FileOpsJobCancel stops the job. Files copied from a CD-ROM are read-only; FileOpsJobClearAttr job, FILEOPS_ATTR_READONLY before the job runs makes every copy changeable (FILEOPS_ATTR_EDITABLE clears all four attribute bits). Whatever a program leaves open is freed when it ends.
|
FileOpsCopyTree, FileOpsMoveTree, and FileOpsDeleteTree do a whole folder in one call, as does FileOpsTreeSize. While one works, the rest of DVX goes on and so do the program's own events, as with DoEvents (in a control's code they wait until the call ends); if the program is ended meanwhile, the call stops and returns False. For a long copy, move, or delete with progress and questions, run the work as a job, a little at a time from a Timer: FileOpsJobCreate, then FileOpsJobAddSource for each file or folder, then FileOpsJobRunFor from the timer until it returns FILEOPS_STEP_DONE or FILEOPS_STEP_CANCELLED. When it returns FILEOPS_STEP_ASK (a file already exists, a file is read-only) or FILEOPS_STEP_ERROR, show FileOpsJobMessage$ and pass the user's choice to FileOpsJobAnswer. FileOpsJobCurrent$, FileOpsJobFilesDone, FileOpsJobFilesTotal, FileOpsJobFoldersTotal, FileOpsJobPercent, FileOpsJobBytesDone, and FileOpsJobBytesTotal show the progress, and FileOpsJobCancel stops the job. A FILEOPS_MEASURE job only counts: the files (FileOpsJobFilesTotal), the folders inside what was added (FileOpsJobFoldersTotal), and the bytes (FileOpsJobBytesTotal), changing nothing and asking nothing; FileOpsJobUnread says how many folders could not be read. A Properties dialog counts a big folder this way without stopping the program. Files copied from a CD-ROM are read-only; FileOpsJobClearAttr job, FILEOPS_ATTR_READONLY before the job runs makes every copy changeable (FILEOPS_ATTR_EDITABLE clears all four attribute bits). Whatever a program leaves open is freed when it ends.
|
||||||
|
|
||||||
.code
|
.code
|
||||||
Dim job As Long
|
Dim job As Long
|
||||||
|
|
@ -5593,7 +5678,7 @@ End Sub
|
||||||
|
|
||||||
With shell.bas in the project, a program works with DVX the way Program Manager does -- a whole desktop can be written in BASIC.
|
With shell.bas in the project, a program works with DVX the way Program Manager does -- a whole desktop can be written in BASIC.
|
||||||
|
|
||||||
Starting programs. ShellRun program, args starts a program (args is what it gets as COMMAND$), runs a DOS program, or opens a document in the program set for its type; it returns True when it started (or the DOS program will run). ShellRunProgram(program, args, folder) does the same the way a Program Manager item does: file names in args, and a document named without a folder, are looked for in folder first ("" for none), and it returns the new program's id (SHELL_RUN_DOS for a DOS program, 0 when it did not start; DVX has already said why). DOS programs are .EXE, .COM, and .BAT files (ShellIsDosProgram(path) says whether a file is one). ShellRunDos program, args, folder, pause runs one with DVX stepped aside: once your procedure has returned, DVX names the programs running besides the desktop, this one included, and asks them to close (each asks about unsaved work; No, or a program kept open, cancels the run); the screen then becomes the DOS screen until the program ends, and DVX comes back with the desktop. folder is where it runs ("" for its own folder); pause True shows "Press any key to return to DVX." when it ends. ShellMinimizeApp id runs that program minimized: its windows become icons, including the ones it opens while it starts. ShellRunCommand(commandLine) runs a whole command line (a program, in double quotes when its name has a blank, then its arguments) and returns the id too. ShellRunDialog$(history) shows DVX's Run dialog offering history (earlier commands, newest first, one per line) and returns what the user chose, "" for Cancel. ShellOpenDocument path opens a document in the program set for its type in the Control Panel.
|
Starting programs. ShellRun program, args starts a program (args is what it gets as COMMAND$), runs a DOS program, or opens a document in the program set for its type; it returns True when it started (or the DOS program will run). ShellRunProgram(program, args, folder) does the same the way a Program Manager item does: file names in args, and a document named without a folder, are looked for in folder first ("" for none), and it returns the new program's id (SHELL_RUN_DOS for a DOS program, 0 when it did not start; DVX has already said why). DOS programs are .EXE, .COM, and .BAT files (ShellIsDosProgram(path) says whether a file is one). ShellRunDos program, args, folder, pause runs one with DVX stepped aside: once your procedure has returned, DVX names the programs running besides the desktop, this one included, and asks them to close (each asks about unsaved work; No, or a program kept open, cancels the run); the screen then becomes the DOS screen until the program ends, and DVX comes back with the desktop. folder is where it runs ("" for its own folder); pause True shows "Press any key to return to DVX." when it ends. ShellRunDosWait(program, args, folder, pause) does the same and waits for it, as SHELL does: your program is not asked to close, its events run meanwhile, and the DOS program's exit code (its ERRORLEVEL) comes back, -1 when it did not run. ShellMinimizeApp id runs that program minimized: its windows become icons, including the ones it opens while it starts. ShellRunCommand(commandLine) runs a whole command line (a program, in double quotes when its name has a blank, then its arguments) and returns the id too. ShellRunDialog$(history) shows DVX's Run dialog offering history (earlier commands, newest first, one per line) and returns what the user chose, "" for Cancel. ShellOpenDocument path opens a document in the program set for its type in the Control Panel.
|
||||||
|
|
||||||
Installed programs. ShellProgramScan(folder, skipPath) finds every program in folder and its subfolders ("" for the APPS folder) and returns a list: ShellProgramCount(list) programs, ShellProgramPath$(list, index) each one's path (index from 0), ShellProgramFree list when done (a list not freed is freed when the program ends). Give ShellAppPath$() as skipPath to leave this program out. ShellProgramName$(path) and ShellProgramDescription$(path) are the name and description a program shows, and ShellIsProgram(path) tells a program from a document. LOADPICTURE of a program gives its icon, so a PictureBox can show a grid of programs with PaintPicture. ShellProgramsFolder$() is the folder the installed programs are in (where ShellProgramScan("") looks), for example to start a Browse for a program there with DlgFileOpenIn$.
|
Installed programs. ShellProgramScan(folder, skipPath) finds every program in folder and its subfolders ("" for the APPS folder) and returns a list: ShellProgramCount(list) programs, ShellProgramPath$(list, index) each one's path (index from 0), ShellProgramFree list when done (a list not freed is freed when the program ends). Give ShellAppPath$() as skipPath to leave this program out. ShellProgramName$(path) and ShellProgramDescription$(path) are the name and description a program shows, and ShellIsProgram(path) tells a program from a document. LOADPICTURE of a program gives its icon, so a PictureBox can show a grid of programs with PaintPicture. ShellProgramsFolder$() is the folder the installed programs are in (where ShellProgramScan("") looks), for example to start a Browse for a program there with DlgFileOpenIn$.
|
||||||
|
|
||||||
|
|
@ -5673,30 +5758,47 @@ End Sub
|
||||||
|
|
||||||
.h2 Add-on Packages
|
.h2 Add-on Packages
|
||||||
|
|
||||||
With pkg.bas in the project, a program installs, removes, and makes add-on packages -- the .DPK files Software installs -- the way Software and Package Builder (both written in BASIC) do. PkgOpen() starts a session (a number; PkgClose ends it), and PkgError$(session) says why the last call that failed did. Lists come back as rows for a ListView's SetRows: cells separated by TAB, each row ending in a line feed.
|
With pkg.bas in the project, a program installs, removes, and makes add-on packages -- the .DPK files Software installs -- the way Software and Package Builder (both written in BASIC) do. PkgOpen() starts a session (a number; PkgClose ends it), and PkgError$(session) says why the last call that failed did. PkgOpenIn(root) starts one on another DVX folder (a copy being made ready on another disk, say): its installed packages and the keys its user folder trusts. Lists come back as rows for a ListView's SetRows: cells separated by TAB, each row ending in a line feed.
|
||||||
|
|
||||||
What is installed. PkgInstalledRows$ lists every installed package (DVX itself is kpunch.dvx): id, name, version, kind, and summary. A package is named by its id, vendor.name (acme.chess). PkgField$(s, id, field) gives one detail (PKG_FIELD_NAME, PKG_FIELD_VERSION, PKG_FIELD_PUBLISHER, PKG_FIELD_SIGNER_NAME, ...); PkgFilesRows$, PkgDependsRows$, and PkgNeededBy$ its files, what it needs, and what needs it. PkgFileOwner$(s, fileName) is the other way: the id of the package that put a file there (its name from the DVX folder, such as "SHARED\FONTS\KPUNCH\ARIMO.DFN"; "" when none did).
|
What is installed. PkgInstalledRows$ lists every installed package (DVX itself is kpunch.dvx): id, name, version, kind, and summary. A package is named by its id, vendor.name (acme.chess). PkgField$(s, id, field) gives one detail (PKG_FIELD_NAME, PKG_FIELD_VERSION, PKG_FIELD_PUBLISHER, PKG_FIELD_SIGNER_NAME, ...); PkgFilesRows$, PkgDependsRows$, and PkgNeededBy$ its files, what it needs, and what needs it. PkgFileOwner$(s, fileName) is the other way: the id of the package that put a file there (its name from the DVX folder, such as "SHARED\FONTS\KPUNCH\ARIMO.DFN"; "" when none did).
|
||||||
|
|
||||||
Installing. PkgOfferFile(s, path) reads a package file and every package beside it (what it may need) as offers, numbered from 0, and returns the number of the one at path (-1 when it cannot be read). PkgOfferField$ and PkgOfferFilesRows$ describe an offer before it is installed; PKG_FIELD_TRUST says whether DVX trusts who signed it. PkgPlan(s, "0") plans installing it with everything it needs, checked against this DVX and the packages installed (False, with PkgError$ saying why, when it cannot be installed); PkgPlanRows$ lists what will go in, PkgPlanBytes the disk space, and PkgPlanRestart whether libraries or widgets are among them (they work from the next time DVX starts). PkgInstallStart(s) then starts the job, and PkgRunFor(s, milliseconds) runs it a little at a time, returning PKG_RUN_BUSY until it is PKG_RUN_DONE or PKG_RUN_FAILED (PkgRunMessage$ says why); PkgRunPercent and PkgRunCurrent$ show how far it is.
|
Jobs. Reading package files, installing, removing, and making and signing packages each take a while (checking a signature alone takes a moment on a 486), so each is a job: a ...Start function starts it, and PkgRunFor(s, milliseconds) runs it a little at a time, returning PKG_RUN_BUSY until it is PKG_RUN_DONE or PKG_RUN_FAILED (PkgRunMessage$ says why); PkgRunPercent and PkgRunCurrent$ show how far it is. A session runs one job at a time.
|
||||||
|
|
||||||
|
Installing. PkgOfferFileStart(s, path) reads a package file and every package beside it (what it may need) as offers, numbered from 0; the one at path is 0, and the job fails when it cannot be read. PkgOfferField$ and PkgOfferFilesRows$ describe an offer before it is installed; PKG_FIELD_TRUST says whether DVX trusts who signed it. PkgPlan(s, "0") plans installing it with everything it needs, checked against this DVX and the packages installed (False, with PkgError$ saying why, when it cannot be installed); PkgPlanRows$ lists what will go in, PkgPlanBytes the disk space, and PkgPlanRestart whether libraries or widgets are among them (they work from the next time DVX starts). PkgInstallStart(s) then starts installing.
|
||||||
|
|
||||||
.code
|
.code
|
||||||
offer = PkgOfferFile(s, path)
|
Function RunJob(s As Long) As Long
|
||||||
If offer >= 0 And PkgPlan(s, CStr(offer)) Then
|
Dim rc As Long
|
||||||
If PkgInstallStart(s) Then
|
|
||||||
Do
|
Do
|
||||||
rc = PkgRunFor(s, 60)
|
rc = PkgRunFor(s, 60)
|
||||||
pbJob.Value = PkgRunPercent(s)
|
pbJob.Value = PkgRunPercent(s)
|
||||||
DoEvents
|
DoEvents
|
||||||
Loop While rc = PKG_RUN_BUSY
|
Loop While rc = PKG_RUN_BUSY
|
||||||
|
RunJob = rc
|
||||||
|
End Function
|
||||||
|
|
||||||
|
If PkgOfferFileStart(s, path) Then
|
||||||
|
If RunJob(s) = PKG_RUN_DONE And PkgPlan(s, "0") Then
|
||||||
|
If PkgInstallStart(s) Then
|
||||||
|
rc = RunJob(s)
|
||||||
|
End If
|
||||||
End If
|
End If
|
||||||
End If
|
End If
|
||||||
.endcode
|
.endcode
|
||||||
|
|
||||||
Removing. PkgDependents$(s, ids) lists the installed packages that need the ones going (they must go too), and PkgOrphans$(s, ids) those only they needed. PkgRemoveStart(s, ids, removeChanged) starts removing, run with PkgRunFor like an install; files changed since they were installed stay unless removeChanged, and PkgRunKept$ lists them.
|
Removing. PkgDependents$(s, ids) lists the installed packages that need the ones going (they must go too), and PkgOrphans$(s, ids) those only they needed. PkgRemoveStart(s, ids, removeChanged) starts removing, run with PkgRunFor like an install; files changed since they were installed stay unless removeChanged, and PkgRunKept$ lists them.
|
||||||
|
|
||||||
Trust. A publisher signs a package with a key so people can tell it came from them. PkgTrustKey(s, keyPath) trusts a publisher's key file (.KEY) from then on.
|
Stopping. PkgRunCancel(s) stops a job after the package it is on: one still being unpacked is left as it was, and one whose files are already in is finished first, so keep calling PkgRunFor until it stops saying PKG_RUN_BUSY. A package being made or signed stops at once, and nothing of it is left behind.
|
||||||
|
|
||||||
From a package server. With pkgnet.bas in the project too, a program gets packages from a package server the way Software does. PkgNetOpen("") returns a handle on the server DVX's settings name, with the account they give; PkgNetClose frees it. PkgNetServer$, PkgNetPort, PkgNetUser$, and PkgNetPassword$ read those settings (PkgNetUrl$ is the address they make), and PkgNetSaveSettings(server, port, user, password) changes them. A job is started with PkgNetCatalogue (every package the server offers), PkgNetUpdates (what it has that is newer than the packages installed, DVX itself among them), or PkgNetFetch(n, ids) (packages by id, with everything they need that is not installed), and run with PkgNetRunFor(n, milliseconds) until it is PKGNET_DONE or PKGNET_FAILED (PkgNetError$ says why); PkgNetPercent and PkgNetCurrent$ show how far a fetch is. PkgNetRows$ lists the answer (id, name, version, kind, summary, size, and the version installed, if any), and PkgNetField$ and PkgNetDependsRows$ describe one package in it. Each package fetched is checked against the size and fingerprint the server gives, and the server's signature of it comes with it, so it is trusted or not exactly as a package file is. What was fetched is in PkgNetDir$: PkgOfferFolder(s, folder) reads every package there as the offers, and PkgOfferIndex(s, id) gives the number of the one wanted, to plan and install as above; PkgNetClear removes them afterwards. With an account (the settings', or PkgNetAccount(n, email, password) for another server), PkgNetUpload(n, path) publishes a package file for the server's review, and PkgNetMine(n) asks what the account has sent: PkgNetMineRows$ lists it (id, name, version, state, and the review's note); both sign in first.
|
Trust. A publisher signs a package with a key so people can tell it came from them. PkgTrustKey(s, keyPath) trusts a publisher's key file (.KEY) from then on. PkgKeysRows$(s) lists the keys DVX trusts (name, id, and "system" for DVX's own or "user" for one trusted this way), and PkgUntrustKey(s, keyId) stops trusting one of the user's.
|
||||||
|
|
||||||
|
Checking a package. PkgVerifyStart(s, path, keyPath) checks a package file without installing it, a job run with PkgRunFor: every file in it against the size and fingerprint its manifest lists, so a damaged or changed package is found. With keyPath (a .KEY file) the package must also be signed by that key, in it or beside it, or nothing starts.
|
||||||
|
|
||||||
|
Signing any data. PkgSignData$(s, bytes, secretPath) signs any bytes (a file read into a string, a message) with a secret key file, giving the signature as text; PkgCheckData(s, bytes, signature, keyPath) says whether a signature was made over those bytes by the key in keyPath, or with keyPath "" by a key DVX trusts.
|
||||||
|
|
||||||
|
Plain ZIP files. A session also lists, unpacks, and makes ordinary .ZIP files, as jobs run with PkgRunFor: PkgZipListStart(s, zipPath) reads one to its end, every file in it checked; PkgZipExtractStart(s, zipPath, folder) unpacks it into folder (a name that would leave folder stops it); PkgZipCreateStart(s, zipPath, files) makes one from rows of a name in it and the file it comes from (as PkgFolderRows$ gives them). PkgZipRows$(s) lists what the last of them read or packed: name, size, date, and "folder" for a folder.
|
||||||
|
|
||||||
|
From a package server. With pkgnet.bas in the project too, a program gets packages from a package server the way Software does. PkgNetOpen("") returns a handle on the server DVX's settings name, with the account they give; PkgNetClose frees it. PkgNetServer$, PkgNetPort, PkgNetUser$, and PkgNetPassword$ read those settings (PkgNetUrl$ is the address they make), and PkgNetSaveSettings(server, port, user, password) changes them. A job is started with PkgNetCatalogue (every package the server offers), PkgNetUpdates (what it has that is newer than the packages installed, DVX itself among them), or PkgNetFetch(n, ids) (packages by id, with everything they need that is not installed), and run with PkgNetRunFor(n, milliseconds) until it is PKGNET_DONE or PKGNET_FAILED (PkgNetError$ says why); PkgNetPercent and PkgNetCurrent$ show how far a fetch is. PkgNetRows$ lists the answer (id, name, version, kind, summary, size, and the version installed, if any), and PkgNetField$ and PkgNetDependsRows$ describe one package in it (PKG_FIELD_SHA256 is its file's fingerprint, PKG_FIELD_REPO_SIGNED whether the repository signed it). PkgNetOpenIn(server, root, downloadDir) works for another DVX folder and fetches into another folder. Each package fetched is checked against the size and fingerprint the server gives, and the server's signature of it comes with it, so it is trusted or not exactly as a package file is. What was fetched is in PkgNetDir$: PkgOfferFolderStart(s, folder) reads every package there as the offers (a job), and PkgOfferIndex(s, id) gives the number of the one wanted, to plan and install as above; PkgNetClear removes them afterwards. With an account (the settings', or PkgNetAccount(n, email, password) for another server), PkgNetUpload(n, path) publishes a package file for the server's review, and PkgNetMine(n) asks what the account has sent: PkgNetMineRows$ lists it (id, name, version, state, and the review's note); both sign in first.
|
||||||
|
|
||||||
.code
|
.code
|
||||||
n = PkgNetOpen("")
|
n = PkgNetOpen("")
|
||||||
|
|
@ -5707,17 +5809,18 @@ If PkgNetFetch(n, "acme.chess") Then
|
||||||
DoEvents
|
DoEvents
|
||||||
Loop While rc = PKGNET_BUSY
|
Loop While rc = PKGNET_BUSY
|
||||||
End If
|
End If
|
||||||
If rc = PKGNET_DONE And PkgOfferFolder(s, PkgNetDir$(n)) > 0 Then
|
If rc = PKGNET_DONE And PkgOfferFolderStart(s, PkgNetDir$(n)) Then
|
||||||
|
rc = RunJob(s)
|
||||||
offer = PkgOfferIndex(s, "acme.chess")
|
offer = PkgOfferIndex(s, "acme.chess")
|
||||||
' plan and install offer as above, then PkgNetClear n
|
' plan and install offer as above, then PkgNetClear n
|
||||||
End If
|
End If
|
||||||
.endcode
|
.endcode
|
||||||
|
|
||||||
Making packages. PkgBuild(s, template, files, out, secret) builds a package: template is a settings file with a [package] section (id, name, version, kind, and summary, publisher, author, licence, homepage, dvx as wanted) and a [depends] section (id = oldest version for each package it needs); files are rows of a name in the package (from the DVX folder, such as APPS\ACME\CHESS\CHESS.APP) and the file it is made from; secret is the secret key file (.SEC) to sign with, or "". PkgFolderRows$(s, folder, prefix) gives those rows for every file in a folder, and PkgKindFolder$(kind, id) the folder a package of that kind keeps its files in. PkgSign signs a package already built, PkgKeygen(s, name, base) makes a key pair (base.KEY to give out, base.SEC to keep private), and PkgKeyInfo$ reads a key file's name and id. PkgIdValid and PkgVersionValid check an id and a version before building; PkgKindName$ lists the kinds.
|
Making packages. PkgBuildStart(s, template, files, out, secret) starts building a package, a job run with PkgRunFor like an install (PkgRunCurrent$ is the file it is on): template is a settings file with a [package] section (id, name, version, kind, and summary, publisher, author, licence, homepage, dvx as wanted) and a [depends] section (id = oldest version for each package it needs); files are rows of a name in the package (from the DVX folder, such as APPS\ACME\CHESS\CHESS.APP) and the file it is made from; secret is the secret key file (.SEC) to sign with, or "". PkgFolderRows$(s, folder, prefix) gives those rows for every file in a folder, and PkgKindFolder$(kind, id) the folder a package of that kind keeps its files in. PkgSignStart(s, path, secret, detached) signs a package already built: into it, a job run the same way, or beside it (NAME.SIG) when detached, done at once. PkgKeygen(s, name, base) makes a key pair (base.KEY to give out, base.SEC to keep private), and PkgKeyInfo$ reads a key file's name and id. PkgIdValid and PkgVersionValid check an id and a version before building; PkgVersionCompare(a, b) says which of two versions is newer (-1, 0, or 1: "1.10.0" is newer than "1.9.2"); PkgKindName$ lists the kinds.
|
||||||
|
|
||||||
.h2 Web Requests and JSON
|
.h2 Web Requests and JSON
|
||||||
|
|
||||||
With http.bas in the project, a program asks web servers for pages, files, and web services' answers. HttpOpen(method, url) makes a request ("GET" to fetch, "POST" to send) and returns a handle; HttpClose frees it. Before starting it, HttpAddHeader adds a header, HttpSetBody gives a body to send with its type ("application/json", say), HttpSaveTo writes the answer's body to a file instead of keeping it (with keepPart True, a file left part-way is finished rather than started again), and HttpSetTimeout sets how many seconds without progress mean the server is not answering (30 to start with). HttpStart starts it (False for an address that is not http://; there is no encryption), and HttpRunFor(h, milliseconds) runs it a little at a time, returning HTTP_BUSY until it is HTTP_DONE or HTTP_FAILED:
|
With http.bas in the project, a program asks web servers for pages, files, and web services' answers. HttpOpen(method, url) makes a request ("GET" to fetch, "POST" to send) and returns a handle; HttpClose frees it. Before starting it, HttpAddHeader adds a header, HttpSetBody gives a body to send with its type ("application/json", say), HttpSetBodyFile sends a file's bytes instead (a piece at a time, so a file of any size), HttpSaveTo writes the answer's body to a file instead of keeping it (with keepPart True, a file left part-way is finished rather than started again), and HttpSetTimeout sets how many seconds without progress mean the server is not answering (30 to start with). HttpStart starts it (False for an address that is not http://; there is no encryption), and HttpRunFor(h, milliseconds) runs it a little at a time, returning HTTP_BUSY until it is HTTP_DONE or HTTP_FAILED:
|
||||||
|
|
||||||
.code
|
.code
|
||||||
h = HttpOpen("GET", "http://example.com/news.txt")
|
h = HttpOpen("GET", "http://example.com/news.txt")
|
||||||
|
|
@ -5735,7 +5838,7 @@ HttpClose h
|
||||||
|
|
||||||
HTTP_DONE means the server answered: HttpStatus is how (200 OK, 404 not found, ...), HttpBody$ what it sent, HttpReplyHeader$ one of its headers, and HttpUrl$ where the answer came from when the server sent the request elsewhere (redirects are followed). HTTP_FAILED means no answer came, and HttpError$ says why. HttpBytes and HttpTotal (-1 until the server says) show progress.
|
HTTP_DONE means the server answered: HttpStatus is how (200 OK, 404 not found, ...), HttpBody$ what it sent, HttpReplyHeader$ one of its headers, and HttpUrl$ where the answer came from when the server sent the request elsewhere (redirects are followed). HTTP_FAILED means no answer came, and HttpError$ says why. HttpBytes and HttpTotal (-1 until the server says) show progress.
|
||||||
|
|
||||||
With json.bas in the project, JsonParse(text) reads JSON -- what most web services answer with -- and returns a handle (0 when it is not JSON: JsonError$ says why and on which line); JsonFree frees it. The other functions find a value by its path from the top: member names and item numbers from 0, such as "packages[3].version". JsonType says what is there (JSON_STRING, JSON_NUMBER, JSON_ARRAY, ..., JSON_NONE for nothing), JsonText$ gives a string, JsonNumber a number, JsonBool True or False, JsonCount how many items an array or members an object has, and JsonKey$(h, path, i) the name of an object's member i. JsonQuote$(text) puts text in quotes with its special characters escaped, for building JSON to send:
|
With json.bas in the project, JsonParse(text) reads JSON -- what most web services answer with -- and returns a handle (0 when it is not JSON: JsonError$ says why and on which line); JsonFree frees it. The other functions find a value by its path from the top: member names and item numbers from 0, such as "packages[3].version". JsonType says what is there (JSON_STRING, JSON_NUMBER, JSON_ARRAY, ..., JSON_NONE for nothing), JsonText$ gives a string, JsonNumber a number, JsonBool True or False, JsonCount how many items an array or members an object has, and JsonKey$(h, path, i) the name of an object's member i. JsonQuote$(text) puts text in quotes with its special characters escaped, for building JSON to send. Text comes and goes as DVX characters: an accented letter or a line-drawing character in a reply is the same character in JsonText$ ("?" stands for one DVX does not have), and JsonQuote$ writes such characters so that any web service reads them right:
|
||||||
|
|
||||||
.code
|
.code
|
||||||
h = JsonParse(HttpBody$(req))
|
h = JsonParse(HttpBody$(req))
|
||||||
|
|
@ -5766,7 +5869,7 @@ The pointer and the keyboard: SysMouseX() and SysMouseY() are where the mouse po
|
||||||
|
|
||||||
Memory: SysMemoryTotalKb() is the computer's memory in KB and SysMemoryFreeKb() how much of it is free (-1 when it cannot be told).
|
Memory: SysMemoryTotalKb() is the computer's memory in KB and SysMemoryFreeKb() how much of it is free (-1 when it cannot be told).
|
||||||
|
|
||||||
Libraries: some parts of DVX are optional (Setup's "What to install" page; Software lists each as one package), and so are their libraries: Networking has dvxnet, http, and pkgnet (net.bas, http.bas, pkgnet.bas), Databases has dvxsql (sql.bas), and Communications has conn and xfer (conn.bas, xfer.bas). SysLibraryLoaded(libName) is True when the library a DECLARE LIBRARY line names (any case) is installed and was started with DVX. A program that can do without such a library asks first and leaves out what needs it: calling a function of a library that is not there stops the program with "External function not found". Software, for one, keeps its package server pages off without pkgnet.
|
Libraries: some parts of DVX are optional (Setup's "What to install" page; Software lists each as one package), and so are their libraries: Networking has dvxnet, http, and pkgnet (net.bas, http.bas, pkgnet.bas), Databases has dvxsql (sql.bas), and Communications has conn and xfer (conn.bas, xfer.bas). SysLibraryLoaded(libName) is True when the library a DECLARE LIBRARY line names (any case) is installed (DVX loads a library when it is first needed, so asking loads it). A program that can do without such a library asks first and leaves out what needs it: calling a function of a library that is not there stops the program with "External function not found". Software, for one, keeps its package server pages off without pkgnet.
|
||||||
|
|
||||||
.code
|
.code
|
||||||
If SysLibraryLoaded("pkgnet") Then
|
If SysLibraryLoaded("pkgnet") Then
|
||||||
|
|
@ -5915,6 +6018,26 @@ Sub Timer1_Timer ()
|
||||||
End Sub
|
End Sub
|
||||||
.endcode
|
.endcode
|
||||||
|
|
||||||
|
A program can also hear of every change instead of reading the joystick: JoyWatch joy, True starts keeping that joystick's changes (False stops), and JoyEvent() gives the next one since it was last asked, each once and in order (the last 64 are kept): JOY_EVENT_AXIS (an axis moved), JOY_EVENT_BUTTON_DOWN, JOY_EVENT_BUTTON_UP, JOY_EVENT_HAT, or JOY_EVENT_NONE when nothing has changed. JoyEventJoy() and JoyEventIndex() say which joystick and which axis, button, or hat, and JoyEventValue() an axis's new position or a hat's direction. A button pressed and let go between two Timer ticks still gives both.
|
||||||
|
|
||||||
|
.code
|
||||||
|
Sub Form1_Load ()
|
||||||
|
ok = JoyWatch(1, True)
|
||||||
|
End Sub
|
||||||
|
|
||||||
|
Sub Timer1_Timer ()
|
||||||
|
Dim kind As Long
|
||||||
|
|
||||||
|
kind = JoyEvent()
|
||||||
|
|
||||||
|
Do While kind <> JOY_EVENT_NONE
|
||||||
|
If kind = JOY_EVENT_BUTTON_DOWN And JoyEventIndex() = 1 Then FireShot
|
||||||
|
If kind = JOY_EVENT_AXIS And JoyEventIndex() = 1 Then shipSpeed = JoyEventValue() \ 8192
|
||||||
|
kind = JoyEvent()
|
||||||
|
Loop
|
||||||
|
End Sub
|
||||||
|
.endcode
|
||||||
|
|
||||||
When no joystick is connected the keyboard stands in for one (the arrow keys, with Left Ctrl, Space, Left Shift, and Z as buttons), and it only answers while one of the program's windows is active. Joysticks are calibrated, and the keyboard joystick's keys chosen, on the Control Panel's Game Controllers tab. QBasic's STICK and STRIG work too, without joystick.bas.
|
When no joystick is connected the keyboard stands in for one (the arrow keys, with Left Ctrl, Space, Left Shift, and Z as buttons), and it only answers while one of the program's windows is active. Joysticks are calibrated, and the keyboard joystick's keys chosen, on the Control Panel's Game Controllers tab. QBasic's STICK and STRIG work too, without joystick.bas.
|
||||||
|
|
||||||
joystick.bas can also set joysticks up, as the Control Panel does. JoyHats(joy) counts the hats, JoyAxisPresent(joy, axis) is True when an axis answers, JoyConnected(joy) is False once a joystick stops answering, and JoyCanCalibrate(joy) is True for one with analog axes. For calibrating, JoyRawAxis(joy, axis) gives an axis's raw reading and JoyRawButtons(joy) the buttons held (1 for button 1, 2 for button 2, 4 for button 3, ... added up); JoySetCalibration(joy, axis, minV, centerV, maxV) takes the readings at one end, the middle, and the other end, and JoyClearCalibration(joy, axis) lets the axis find its own range again. JoyDeadZone(joy) and JoySetDeadZone(joy, percent) are the dead zone round the middle (0 to 50 percent). JoyChoiceCount, JoyChoiceLabel$, JoyChoice, and JoySetChoice list and change the game port's mode (JOY_CHOICE_GAMEPORT) and when the keyboard joystick stands in (JOY_CHOICE_FALLBACK); JoyKeyCount and JoyKeyName$(index) list the keys the keyboard joystick can use, and JoyKbdSlot(slot) and JoySetKbdSlot(slot, keyIndex) its keys for up, down, left, right, and buttons 1 to 4 (slots 0 to 7). These lists are numbered from 0, so they fill a DropDown in order. JoyKeyIndex(keyName) gives a key's number by its name (-1 when unknown), JoyKeyCode(index) its raw key code, which GameKeyDown in game.bas takes, and JoyKeyNameOf$(rawCode) the name of a raw key code. JoyIsKeyboard(joy) is True for the keyboard standing in for a joystick, and JoyCalibration(joy, axis, part) reads back an axis's calibration: part JOY_CAL_MIN, JOY_CAL_CENTER, or JOY_CAL_MAX of the readings set with JoySetCalibration, or of the range the axis found by itself (0 for a joystick that cannot be calibrated). JoySetting$(key, default) and JoySetSetting key, value reach any other joystick setting; JOY_KEY_POLL_HZ is the key of how often joysticks are read, JOY_POLL_HZ_MIN to JOY_POLL_HZ_MAX times a second (JOY_POLL_HZ_DEFAULT normally), and dead zones go up to JOY_DEADZONE_MAX percent (JOY_DEADZONE_DEFAULT normally). After a new game port mode, JoyRescan() looks for joysticks again; it returns the new count, or -1 while another program is using a joystick. Every change applies at once; JoySettingsStore handle puts them all into a settings handle from prefs.bas for the program to save, and JoySettingsLoad handle takes them back from one, to undo.
|
joystick.bas can also set joysticks up, as the Control Panel does. JoyHats(joy) counts the hats, JoyAxisPresent(joy, axis) is True when an axis answers, JoyConnected(joy) is False once a joystick stops answering, and JoyCanCalibrate(joy) is True for one with analog axes. For calibrating, JoyRawAxis(joy, axis) gives an axis's raw reading and JoyRawButtons(joy) the buttons held (1 for button 1, 2 for button 2, 4 for button 3, ... added up); JoySetCalibration(joy, axis, minV, centerV, maxV) takes the readings at one end, the middle, and the other end, and JoyClearCalibration(joy, axis) lets the axis find its own range again. JoyDeadZone(joy) and JoySetDeadZone(joy, percent) are the dead zone round the middle (0 to 50 percent). JoyChoiceCount, JoyChoiceLabel$, JoyChoice, and JoySetChoice list and change the game port's mode (JOY_CHOICE_GAMEPORT) and when the keyboard joystick stands in (JOY_CHOICE_FALLBACK); JoyKeyCount and JoyKeyName$(index) list the keys the keyboard joystick can use, and JoyKbdSlot(slot) and JoySetKbdSlot(slot, keyIndex) its keys for up, down, left, right, and buttons 1 to 4 (slots 0 to 7). These lists are numbered from 0, so they fill a DropDown in order. JoyKeyIndex(keyName) gives a key's number by its name (-1 when unknown), JoyKeyCode(index) its raw key code, which GameKeyDown in game.bas takes, and JoyKeyNameOf$(rawCode) the name of a raw key code. JoyIsKeyboard(joy) is True for the keyboard standing in for a joystick, and JoyCalibration(joy, axis, part) reads back an axis's calibration: part JOY_CAL_MIN, JOY_CAL_CENTER, or JOY_CAL_MAX of the readings set with JoySetCalibration, or of the range the axis found by itself (0 for a joystick that cannot be calibrated). JoySetting$(key, default) and JoySetSetting key, value reach any other joystick setting; JOY_KEY_POLL_HZ is the key of how often joysticks are read, JOY_POLL_HZ_MIN to JOY_POLL_HZ_MAX times a second (JOY_POLL_HZ_DEFAULT normally), and dead zones go up to JOY_DEADZONE_MAX percent (JOY_DEADZONE_DEFAULT normally). After a new game port mode, JoyRescan() looks for joysticks again; it returns the new count, or -1 while another program is using a joystick. Every change applies at once; JoySettingsStore handle puts them all into a settings handle from prefs.bas for the program to save, and JoySettingsLoad handle takes them back from one, to undo.
|
||||||
|
|
@ -5954,9 +6077,22 @@ End Sub
|
||||||
|
|
||||||
WAV files must be uncompressed (8 or 16 bits, mono or stereo); VOC files may be 8- or 16-bit. QBasic's BEEP, SOUND, and PLAY work too, without sound.bas.
|
WAV files must be uncompressed (8 or 16 bits, mono or stereo); VOC files may be 8- or 16-bit. QBasic's BEEP, SOUND, and PLAY work too, without sound.bas.
|
||||||
|
|
||||||
PlayTriangle(frequency, milliseconds, volume) plays a triangle tone, softer than PlayTone's square tone. SoundGetVolume() gives the volume of everything the program plays. SoundCaps(field) tells what the sound device can do, by name in any case: "dac" (it plays recorded sound), "synth" (it has a music synthesizer), "tone" (it plays tones on a tone generator), "running", and "record" (it records sound) are True or False; "voices" (how many sounds play at once), "rate" (hertz), "bits", "channels", and "recordsources" (the inputs it records from) are numbers. SoundPlayEvent eventIdx, wait plays one of DVX's own event sounds (SOUND_EVENT_DEFAULT, SOUND_EVENT_ERROR, SOUND_EVENT_WARNING, ...), the one the settings choose; with wait True it returns once the sound has played.
|
PlayTriangle(frequency, milliseconds, volume) plays a triangle tone, softer than PlayTone's square tone. SoundGetVolume() gives the volume of everything the program plays. SoundCaps(field) tells what the sound device can do, by name in any case: "dac" (it plays recorded sound), "synth" (it has a music synthesizer), "tone" (it plays tones on a tone generator), "running", and "record" (it records sound) are True or False; "voices" (how many sounds play at once), "rate" (hertz), "bits", "channels", and "recordsources" (the inputs it records from) are numbers. SoundDriver$() is the short name of the sound driver playing ("sb", "speaker", ...; "" when none is), and SoundStats(field) what the mixer has done since the device started: "renders" (blocks of sound made), "frames" (samples made), "underruns" (stream samples that were due but not yet written), "clipped" (samples too loud), "dropped" (sounds dropped because too many played at once), and "playing" (voices playing now). SoundPlayEvent eventIdx, wait plays one of DVX's own event sounds (SOUND_EVENT_DEFAULT, SOUND_EVENT_ERROR, SOUND_EVENT_WARNING, ...), the one the settings choose; with wait True it returns once the sound has played (DVX and the program's events go on meanwhile, as in DoEvents; in a control file's code, and once the program is ending, it does not wait).
|
||||||
|
|
||||||
A sound played often is best loaded once. SoundLoad(fileName) loads a WAV or VOC file and returns a sound number, or 0 (SoundError$() says why); SoundCreate(data, rate, bits, channels) makes a sound from raw sound data in a string: 8-bit samples (0 to 255, 128 is silence) or 16-bit ones (two bytes each, the low byte first), mono or stereo (left, right, left, ...), rate times a second (SOUND_RATE_MIN to SOUND_RATE_MAX). SoundPlay(sound, volume, pan, loopIt) plays it on a new voice and returns the voice number; pan goes from SOUND_PAN_LEFT (-100) through SOUND_PAN_CENTER (0) to SOUND_PAN_RIGHT (100). SoundLength(sound) is its length in milliseconds and SoundFree sound stops its voices and forgets it. While a voice plays, SoundSetVolume voice, volume, SoundSetPan voice, pan, and SoundSetPitch voice, percent change it (pitch is a percent of the recorded speed: SOUND_PITCH_NORMAL is 100, 200 is an octave higher, from SOUND_PITCH_MIN 25 to SOUND_PITCH_MAX 400), and SoundSetFrequency voice, hertz changes a tone's frequency. SoundPause voice holds a sound's voice where it is, silent, and SoundResume voice goes on from there (a tone cannot be paused). A program's sounds are freed when it ends.
|
A sound played often is best loaded once. SoundLoad(fileName) loads a WAV or VOC file and returns a sound number, or 0 (SoundError$() says why); it reads and decodes the whole file before it returns, so for a long recording (a minute or more) use MusicLoad and MusicPlay (music.bas), which play a WAV or VOC file as they read it; SoundCreate(data, rate, bits, channels) makes a sound from raw sound data in a string: 8-bit samples (0 to 255, 128 is silence) or 16-bit ones (two bytes each, the low byte first), mono or stereo (left, right, left, ...), rate times a second (SOUND_RATE_MIN to SOUND_RATE_MAX). SoundLoadData(bytes) loads one from the bytes of a WAV or VOC file held in a string, such as ResGetData$ (resource.bas) or HttpBody$ (http.bas) gives. SoundPlay(sound, volume, pan, loopIt) plays it on a new voice and returns the voice number; pan goes from SOUND_PAN_LEFT (-100) through SOUND_PAN_CENTER (0) to SOUND_PAN_RIGHT (100). SoundLength(sound) is its length in milliseconds, SoundRate(sound), SoundBits(sound), and SoundChannels(sound) how it was recorded, and SoundFree sound stops its voices and forgets it. While a voice plays, SoundSetVolume voice, volume, SoundSetPan voice, pan, and SoundSetPitch voice, percent change it (pitch is a percent of the recorded speed: SOUND_PITCH_NORMAL is 100, 200 is an octave higher, from SOUND_PITCH_MIN 25 to SOUND_PITCH_MAX 400), and SoundSetFrequency voice, hertz changes a tone's frequency. SoundPause voice holds a sound's voice where it is, silent, and SoundResume voice goes on from there (a tone cannot be paused). A program's sounds are freed when it ends.
|
||||||
|
|
||||||
|
To hear of every sound ending, call SoundEnded() from a Timer: it gives the next of the program's voices that ended since it was last asked (each once, in the order they ended; the last 64 are kept), or 0 when none has, and SoundEndReason() says why that one ended: SOUND_END_FINISHED (it played to its end), SOUND_END_STOPPED (StopWave, StopAllSounds, or its sound was freed), or SOUND_END_DROPPED (too many sounds played at once and it was the quietest).
|
||||||
|
|
||||||
|
.code
|
||||||
|
Sub Timer1_Timer ()
|
||||||
|
Dim v As Long
|
||||||
|
v = SoundEnded()
|
||||||
|
Do While v <> 0
|
||||||
|
If v = engine And SoundEndReason() = SOUND_END_FINISHED Then engine = SoundPlay(idle, 60, 0, True)
|
||||||
|
v = SoundEnded()
|
||||||
|
Loop
|
||||||
|
End Sub
|
||||||
|
.endcode
|
||||||
|
|
||||||
.code
|
.code
|
||||||
Dim Shared laser As Long
|
Dim Shared laser As Long
|
||||||
|
|
@ -5983,7 +6119,16 @@ Sub Timer1_Timer ()
|
||||||
End Sub
|
End Sub
|
||||||
.endcode
|
.endcode
|
||||||
|
|
||||||
A program can record sound from the sound card's microphone, line, or CD input into a WAV file. SoundCaps("record") says whether the card can, and SoundCaps("recordsources") which inputs it has (the bit 2 ^ RECORD_MIC, RECORD_LINE, or RECORD_CD set for each). RecordStart(fileName, rate, bits, channels, inputSource) starts recording (0 for 22,050 Hz, 16-bit, mono) and returns True, or False with SoundError$ saying why; the card records at its nearest, which RecordRate(), RecordBits(), and RecordChannels() tell. While it records, RecordLength() is how long the recording is (milliseconds) and RecordLevel() the loudest sound since it was last asked (percent: a level meter); RecordLost() counts samples dropped because DVX was too busy. RecordStop() ends it and returns True when the file was all written; Recording() is True in between. Most sound cards record or play, not both, so nothing plays while a program records, and one program records at a time. A program's recording ends when the program does.
|
A melody of tones -- a game's tune or a sound effect -- plays without a gap or a stumble in a note queue. NotesOpen(wave, volume) opens one in a waveform (SOUND_WAVE_SQUARE, SOUND_WAVE_TRIANGLE, or SOUND_WAVE_NOISE) and returns its number, or 0. NotesAdd(queue, hertz, milliseconds) adds a tone (0 hertz for a rest; fractions count) and returns True, or False when 256 are already waiting; each lasts exactly as long as asked, as long as the next is added before the one playing ends. NotesSetTone(queue, wave, volume) changes the waveform and volume (percent of the queue's) of the tones added from then on, so they change in time with the tune. NotesQueued(queue) is how many tones and rests have not finished (0 once it has gone quiet) and NotesSpace(queue) how many more fit; NotesVoice(queue) is its voice, for SoundSetVolume and SoundSetPan. NotesClear queue silences it and forgets what it holds, and NotesClose queue closes it. PLAY's V and @ commands give PLAY tunes a volume and a waveform too.
|
||||||
|
|
||||||
|
.code
|
||||||
|
q = NotesOpen(SOUND_WAVE_TRIANGLE, 80)
|
||||||
|
For f = 200 To 800 Step 50
|
||||||
|
ok = NotesAdd(q, f, 15) ' a rising sweep, 15 ms a step
|
||||||
|
Next f
|
||||||
|
.endcode
|
||||||
|
|
||||||
|
A program can record sound from the sound card's microphone, line, or CD input into a WAV file, or into memory. SoundCaps("record") says whether the card can, and SoundCaps("recordsources") which inputs it has (the bit 2 ^ RECORD_MIC, RECORD_LINE, or RECORD_CD set for each). RecordStart(fileName, rate, bits, channels, inputSource) starts recording (0 for 22,050 Hz, 16-bit, mono) and returns True, or False with SoundError$ saying why; the card records at its nearest, which RecordRate(), RecordBits(), and RecordChannels() tell. While it records, RecordLength() is how long the recording is (milliseconds) and RecordLevel() the loudest sound since it was last asked (percent: a level meter); RecordLost() counts samples dropped because DVX was too busy. RecordStop() ends it and returns True when the file was all written; Recording() is True in between. With a file name of "", RecordStart records into memory instead: RecordRead$(maxBytes) takes up to maxBytes of what was recorded and not yet taken, as raw sound data in the recording's format (as SoundCreate and SoundStreamWrite take it), and RecordQueued() is how many bytes are waiting. Take them often, from a Timer: about two seconds are kept, and what does not fit is lost (RecordLost()). A program can show the sound as it comes, change it, or send it over the network. Most sound cards record or play, not both, so nothing plays while a program records, and one program records at a time. A program's recording ends when the program does.
|
||||||
|
|
||||||
.code
|
.code
|
||||||
If RecordStart("NOTE.WAV", 0, 0, 0, RECORD_MIC) Then
|
If RecordStart("NOTE.WAV", 0, 0, 0, RECORD_MIC) Then
|
||||||
|
|
@ -5994,7 +6139,7 @@ End If
|
||||||
|
|
||||||
On a computer with an FM synthesizer chip (AdLib and most Sound Blasters), a program can play instruments of its own. FmInfo$(field) describes the chip: "description" (like "OPL3 FM synthesizer at 388h", "" when there is none), "voices", "free" (voices no program has), "stereo", and "owned" (one program has the whole chip). FmAlloc() takes one of its two-operator voices (0 when none is free), FmWrite(voice, register, value) sets one of the voice's registers (the FM_ constants: FM_MOD_CHAR, FM_CAR_LEVEL, FM_FNUM_LOW, FM_KEY_BLOCK with FM_KEY_ON, ...; True when it worked), and FmFree voice gives it back. A player of chip music files can take the whole chip with FmRawAcquire() (False while another program has it), set any register with FmRawWrite(register, value) (bank * FM_BANK_REGS + register; the second bank only on an OPL3) and give it back with FmRawRelease. A frequency number for FM_FNUM_LOW and FM_KEY_BLOCK is hertz * 2 ^ 20 / (FM_CHIP_HZ * 2 ^ block).
|
On a computer with an FM synthesizer chip (AdLib and most Sound Blasters), a program can play instruments of its own. FmInfo$(field) describes the chip: "description" (like "OPL3 FM synthesizer at 388h", "" when there is none), "voices", "free" (voices no program has), "stereo", and "owned" (one program has the whole chip). FmAlloc() takes one of its two-operator voices (0 when none is free), FmWrite(voice, register, value) sets one of the voice's registers (the FM_ constants: FM_MOD_CHAR, FM_CAR_LEVEL, FM_FNUM_LOW, FM_KEY_BLOCK with FM_KEY_ON, ...; True when it worked), and FmFree voice gives it back. A player of chip music files can take the whole chip with FmRawAcquire() (False while another program has it), set any register with FmRawWrite(register, value) (bank * FM_BANK_REGS + register; the second bank only on an OPL3) and give it back with FmRawRelease. A frequency number for FM_FNUM_LOW and FM_KEY_BLOCK is hertz * 2 ^ 20 / (FM_CHIP_HZ * 2 ^ block).
|
||||||
|
|
||||||
On a wavetable sound card (a Gravis UltraSound, a Sound Blaster AWE32 or AWE64), a program can play recorded sounds on the card's own voices from the card's memory. WtInfo$(field) describes the card: "description" ("" when there is none), "voices", "free" (voices no program has), "memory" and "freememory" (bytes), "level" (how many centibels louder a voice at full volume is than the loudest recorded sound the card plays: 0 when it is the same or not known), and what the card does itself ("envelopes", "lfos", "filter", "effects", "8bit", "rom"; True or False as text). WtMemAlloc(bytes, bits) takes memory for samples of 8 or 16 bits (its address, -1 when there is no room), WtUpload(address, pcm, bits) copies 16-bit mono samples there (a binary string, two bytes a sample; True when it worked), and WtMemFree address gives it back. WtAlloc() takes a voice (0 when none is free); WtStart(voice, address, startFrame, endFrame, loopStart, loopEnd, loopMode, bits, pitch, attenuation, pan) plays a sample on it from startFrame (0: its first; loopMode 0 plays once, 1 loops, 3 loops until WtRelease; pitch in cents, 0 for 44,100 samples a second and 1200 an octave higher; attenuation in centibels, 0 loudest and 60 half as loud; pan -100 to 100). WtSetPitch, WtSetAttenuation, and WtSetPan change a playing voice, WtStop silences it, WtActive(voice) is True while it sounds, and WtFree voice gives it back. Everything a program took is given back when it ends.
|
On a wavetable sound card (a Gravis UltraSound, a Sound Blaster AWE32 or AWE64), a program can play recorded sounds on the card's own voices from the card's memory. WtInfo$(field) describes the card: "description" ("" when there is none), "voices", "free" (voices no program has), "memory", "freememory" (the largest free block), and "freetotal" (all free, in blocks maybe apart; bytes), "level" (how many centibels louder a voice at full volume is than the loudest recorded sound the card plays: 0 when it is the same or not known), and what the card does itself ("envelopes", "lfos", "filter", "effects", "8bit", "rom"; True or False as text). WtMemAlloc(bytes, bits) takes memory for samples of 8 or 16 bits (its address, -1 when there is no room), WtUpload(address, pcm, bits) copies 16-bit mono samples there (a binary string, two bytes a sample; True when it worked), and WtMemFree address gives it back. WtAlloc() takes a voice (0 when none is free); WtStart(voice, address, startFrame, endFrame, loopStart, loopEnd, loopMode, bits, pitch, attenuation, pan) plays a sample on it from startFrame (0: its first; loopMode 0 plays once, 1 loops, 3 loops until WtRelease; pitch in cents, 0 for 44,100 samples a second and 1200 an octave higher; attenuation in centibels, 0 loudest and 60 half as loud; pan -100 to 100); on a card with a General MIDI sample ROM (WtInfo$("rom")) the address may be WT_ADDR_ROM plus the bytes from the ROM's start. WtSetPitch, WtSetAttenuation, and WtSetPan change a playing voice, WtStop silences it, WtActive(voice) is True while it sounds, and WtFree voice gives it back. WtShape(voice, field, value) shapes the voice's notes as a SoundFont instrument does, one value by name, where the card can (WtInfo$ "envelopes", "lfos", "filter", "effects"): the volume envelope ("voldelay", "volattack", "volhold", "voldecay", "volsustain", "volrelease"), the modulation envelope ("moddelay" to "modrelease", "modenvpitch", "modenvfilter"), the modulation and vibrato LFOs ("modlfodelay", "modlfofreq", "modlfopitch", "modlfofilter", "modlfovolume", "viblfodelay", "viblfofreq", "viblfopitch"), the filter ("filterfc", "filterq"), and the effects ("reverb", "chorus"), in the units sound.bas lists. A value is kept for the voice's next WtStart; the filter, the effects, and the LFO depths also change a note that is sounding. Everything a program took is given back when it ends.
|
||||||
|
|
||||||
sound.bas also changes DVX's own sound settings, as the Control Panel's Sounds page does. DVX plays a sound for each of SoundEventCount() events, numbered from 0: SoundEventLabel$(eventIdx) names one ("Start DVX", "Error", ...) and SoundEventKey$(eventIdx) gives its key in DVX.INI. With a settings handle from prefs.bas (PrefsLoad(PrefsDvxIni$())), SoundEventFile$(handle, eventIdx) is the event's sound file ("" for none) and SoundSetEventFile handle, eventIdx, fileName changes it; after the program saves the file, SoundEventsReload makes DVX play the new sounds. SoundTestFile(fileName) plays a file the way DVX plays its event sounds (0 when it cannot, and SoundError$() says why), and SoundFolder$() is the folder of DVX's own sounds. SoundMasterVolume() and SoundSetMasterVolume percent are the volume of all sound from every program. SoundDriverCount() reads the list of sound drivers and SoundDriverName$(index) names each; SoundDriverAuto$() is the setting that means "find the best one". SoundMidiCount() and SoundMidiLabel$(index) list where MIDI music can play, SoundMidiInCount() and SoundMidiInLabel$(index) where MIDI input can come from, and SoundModulesCount() and SoundModulesLabel$(index) where music modules can play. SoundSettingDriver$(handle), SoundSettingMidi(handle), SoundSettingModules(handle), and SoundSettingVolume(handle) read the sound driver, MIDI choice, modules choice, and master volume from the settings, and SoundSettingsStore handle, driver, midi, modules, volume stores them (a new driver or MIDI choice takes effect when DVX next starts, a modules choice with the next music); SoundSettingMidiIn(handle) and SoundSettingMidiInStore handle, midiIn read and store the MIDI input choice (it takes effect when DVX next starts).
|
sound.bas also changes DVX's own sound settings, as the Control Panel's Sounds page does. DVX plays a sound for each of SoundEventCount() events, numbered from 0: SoundEventLabel$(eventIdx) names one ("Start DVX", "Error", ...) and SoundEventKey$(eventIdx) gives its key in DVX.INI. With a settings handle from prefs.bas (PrefsLoad(PrefsDvxIni$())), SoundEventFile$(handle, eventIdx) is the event's sound file ("" for none) and SoundSetEventFile handle, eventIdx, fileName changes it; after the program saves the file, SoundEventsReload makes DVX play the new sounds. SoundTestFile(fileName) plays a file the way DVX plays its event sounds (0 when it cannot, and SoundError$() says why), and SoundFolder$() is the folder of DVX's own sounds. SoundMasterVolume() and SoundSetMasterVolume percent are the volume of all sound from every program. SoundDriverCount() reads the list of sound drivers and SoundDriverName$(index) names each; SoundDriverAuto$() is the setting that means "find the best one". SoundMidiCount() and SoundMidiLabel$(index) list where MIDI music can play, SoundMidiInCount() and SoundMidiInLabel$(index) where MIDI input can come from, and SoundModulesCount() and SoundModulesLabel$(index) where music modules can play. SoundSettingDriver$(handle), SoundSettingMidi(handle), SoundSettingModules(handle), and SoundSettingVolume(handle) read the sound driver, MIDI choice, modules choice, and master volume from the settings, and SoundSettingsStore handle, driver, midi, modules, volume stores them (a new driver or MIDI choice takes effect when DVX next starts, a modules choice with the next music); SoundSettingMidiIn(handle) and SoundSettingMidiInStore handle, midiIn read and store the MIDI input choice (it takes effect when DVX next starts).
|
||||||
|
|
||||||
|
|
@ -6015,7 +6160,7 @@ Next i
|
||||||
|
|
||||||
With music.bas in the project, a program plays music files: tracker modules (MOD, S3M, XM, and IT files, made of recorded instrument sounds) wherever the computer can play sampled sound (on a Gravis UltraSound's or Sound Blaster AWE's own voices, which takes the computer little time), MIDI files (.MID, types 0 and 1) on the computer's music synthesizer, or on a built-in synthesizer that sounds much the same when there is none, and WAV and VOC sound files wherever the computer can play sampled sound (a long WAV recording is read from the disk as it plays, so it needs no memory). Several programs can play music at once, and everything a program plays stops when it ends.
|
With music.bas in the project, a program plays music files: tracker modules (MOD, S3M, XM, and IT files, made of recorded instrument sounds) wherever the computer can play sampled sound (on a Gravis UltraSound's or Sound Blaster AWE's own voices, which takes the computer little time), MIDI files (.MID, types 0 and 1) on the computer's music synthesizer, or on a built-in synthesizer that sounds much the same when there is none, and WAV and VOC sound files wherever the computer can play sampled sound (a long WAV recording is read from the disk as it plays, so it needs no memory). Several programs can play music at once, and everything a program plays stops when it ends.
|
||||||
|
|
||||||
MusicLoad(fileName) loads a music file and returns its music number, or MUSIC_FAILED (0) when it could not be loaded; MusicError$() then says why. MusicPlay(music, loopIt) plays it from where it is (the start after loading or stopping); with loopIt True it starts over at the end until it is stopped. MusicStop(music) stops and goes back to the start, MusicPause(music) and MusicResume(music) pause and go on, and MusicPlaying(music) is True while it plays. MusicVolume(music, volume) sets its volume in percent (0 to 100). MusicPosition(music) and MusicLength(music) give the place and the length in milliseconds, and MusicSeek(music, milliseconds) goes to a place (a module goes to the start of the part that holds it). MusicFree(music) stops a music and forgets it.
|
MusicLoad(fileName) loads a music file and returns its music number, or MUSIC_FAILED (0) when it could not be loaded; MusicError$() then says why. MusicLoadData(bytes) does the same with the bytes of a music or sound file held in a string (from ResGetData$ or HttpBody$, say). MusicPlay(music, loopIt) plays it from where it is (the start after loading or stopping); with loopIt True it starts over at the end until it is stopped. MusicStop(music) stops and goes back to the start, MusicPause(music) and MusicResume(music) pause and go on, and MusicPlaying(music) is True while it plays. MusicEnded(music) is True once after the music played to its end (not when it was stopped) since MusicPlay started it: a Timer that asks it can start the next tune on time. MusicVolume(music, volume) sets its volume in percent (0 to 100). MusicPosition(music) and MusicLength(music) give the place and the length in milliseconds, and MusicSeek(music, milliseconds) goes to a place (a module goes to the start of the part that holds it). MusicFree(music) stops a music and forgets it.
|
||||||
|
|
||||||
MusicLoadMacro(macro) makes a music from a music macro string, the kind PLAY takes, with its notes on the General MIDI instruments its I commands name (the piano before any I): the tune can then be played in the background, paused, stopped, and made quieter like any music. MIDI music plays on the instruments chosen in Control Panel; MusicSetBank music, bank plays one music on another instrument bank, named as in the banks folders ("" is DVX's standard bank), before the music first plays (MusicError$ says when the bank is missing or damaged). A SoundFont (.SF2 or .SBK) gives the recorded instruments for a wavetable card or the built-in SoundFont synthesizer, as can a Gravis UltraSound's own patches: MusicPatchSet$() names them (their ULTRASND.INI, "" when there are none), and MusicRomBank$() names the instruments built into a Sound Blaster AWE card ("" when Creative's software is not installed).
|
MusicLoadMacro(macro) makes a music from a music macro string, the kind PLAY takes, with its notes on the General MIDI instruments its I commands name (the piano before any I): the tune can then be played in the background, paused, stopped, and made quieter like any music. MIDI music plays on the instruments chosen in Control Panel; MusicSetBank music, bank plays one music on another instrument bank, named as in the banks folders ("" is DVX's standard bank), before the music first plays (MusicError$ says when the bank is missing or damaged). A SoundFont (.SF2 or .SBK) gives the recorded instruments for a wavetable card or the built-in SoundFont synthesizer, as can a Gravis UltraSound's own patches: MusicPatchSet$() names them (their ULTRASND.INI, "" when there are none), and MusicRomBank$() names the instruments built into a Sound Blaster AWE card ("" when Creative's software is not installed).
|
||||||
|
|
||||||
|
|
@ -6129,16 +6274,24 @@ End Sub
|
||||||
.table
|
.table
|
||||||
Function Description
|
Function Description
|
||||||
-------- -----------
|
-------- -----------
|
||||||
HostLookup$(hostName) The address of a name, such as "192.0.2.10", or "" when it is not found.
|
HostLookup$(hostName) The address of a name, such as "192.0.2.10", or "" when it is not found. Waits for the answer; other programs keep running.
|
||||||
|
HostLookupStart(hostName) The same without waiting: a lookup handle (0 when too many are under way).
|
||||||
|
HostLookupPoll(lookup) NET_BUSY while the lookup goes on, NET_DONE once found, NET_FAILED (NetError$ says why).
|
||||||
|
HostLookupResult$(lookup) The address found, once HostLookupPoll says NET_DONE.
|
||||||
|
HostLookupClose lookup Free a lookup handle (or give the lookup up).
|
||||||
HostName$(address) The name of an address; the address itself when no name is known for it.
|
HostName$(address) The name of an address; the address itself when no name is known for it.
|
||||||
NetAddress$() This computer's address ("" while it has none).
|
NetAddress$() This computer's address ("" while it has none).
|
||||||
NetError$() Why the last call failed, in plain words.
|
NetError$() Why the last call failed, in plain words.
|
||||||
TcpConnect(host, port) Connect; a handle, or 0. Waits up to 20 seconds; other programs keep running.
|
TcpConnect(host, port) Connect; a handle, or 0. Waits up to 20 seconds; other programs keep running.
|
||||||
|
TcpConnectStart(host, port) The same without waiting: the connection's handle at once (0 when it cannot start).
|
||||||
|
TcpConnectPoll(handle) NET_BUSY while the host is looked up and the connection made, NET_DONE once it is (the handle then works as TcpConnect's), NET_FAILED (close it).
|
||||||
TcpListen(port) Wait for connections on port; a listening handle, or 0.
|
TcpListen(port) Wait for connections on port; a listening handle, or 0.
|
||||||
TcpAccept(handle) A connection that arrived on a listening handle, or 0 when none is waiting.
|
TcpAccept(handle) A connection that arrived on a listening handle, or 0 when none is waiting.
|
||||||
TcpSend(handle, bytes) Send text or any other bytes; the bytes sent, or -1.
|
TcpSend(handle, bytes) Send text or any other bytes; the bytes sent, or -1. Waits while the connection's buffer is full.
|
||||||
|
TcpSendSome(handle, bytes) Send what the connection takes now, never waiting: the bytes taken from the start (0 while it is full), or -1.
|
||||||
TcpPending(handle) Bytes waiting to be read (-1 for a bad handle).
|
TcpPending(handle) Bytes waiting to be read (-1 for a bad handle).
|
||||||
TcpRecv$(handle) What has arrived (up to 4096 bytes), or "".
|
TcpRecv$(handle) What has arrived (up to 4096 bytes), or "".
|
||||||
|
TcpPeek$(handle) What has arrived (up to 4096 bytes), left waiting for the next TcpRecv$: look for a line's end or a message's length first.
|
||||||
TcpConnected(handle) 1 while the connection is open, 0 once the other side closed it.
|
TcpConnected(handle) 1 while the connection is open, 0 once the other side closed it.
|
||||||
TcpShutdown(handle) Tell the other side nothing more will be sent; what it sends can still be read.
|
TcpShutdown(handle) Tell the other side nothing more will be sent; what it sends can still be read.
|
||||||
TcpPeer$(handle) "address:port" of the other side ("" when not connected).
|
TcpPeer$(handle) "address:port" of the other side ("" when not connected).
|
||||||
|
|
@ -6155,6 +6308,29 @@ End Sub
|
||||||
|
|
||||||
The strings sent and received may hold any bytes, CHR$(0) too, so binary protocols work as well as text. Every connection a program opens is closed for it when the program ends.
|
The strings sent and received may hold any bytes, CHR$(0) too, so binary protocols work as well as text. Every connection a program opens is closed for it when the program ends.
|
||||||
|
|
||||||
|
HostLookup$, TcpConnect, and TcpSend wait until they are done. Other programs keep running meanwhile, but yours does not: its windows do not answer, and the first lookup after DVX starts can also wait for the network to get its settings (DHCP). To stay responsive, start the lookup or connection and check on it from a Timer:
|
||||||
|
|
||||||
|
.code
|
||||||
|
Dim conn As Long
|
||||||
|
|
||||||
|
Sub cmdConnect_Click ()
|
||||||
|
conn = TcpConnectStart("bbs.example.org", 23)
|
||||||
|
Timer1.Enabled = conn <> 0
|
||||||
|
End Sub
|
||||||
|
|
||||||
|
Sub Timer1_Timer ()
|
||||||
|
Select Case TcpConnectPoll(conn)
|
||||||
|
Case NET_DONE
|
||||||
|
Timer1.Enabled = False
|
||||||
|
lblStatus.Caption = "Connected"
|
||||||
|
Case NET_FAILED
|
||||||
|
Timer1.Enabled = False
|
||||||
|
lblStatus.Caption = "Could not connect: " & NetError$()
|
||||||
|
TcpClose conn
|
||||||
|
End Select
|
||||||
|
End Sub
|
||||||
|
.endcode
|
||||||
|
|
||||||
A program can also edit the network settings the way the Control Panel's Network page does, and show the network's state. The settings are kept in DVX's settings file, so NetConfigLoad and NetConfigStore take a handle from prefs.bas (add prefs.bas to the project too). NetConfigLoad gives a settings handle (0 when none is free); read and change its fields by name, check them, store them, and restart the network only when they changed:
|
A program can also edit the network settings the way the Control Panel's Network page does, and show the network's state. The settings are kept in DVX's settings file, so NetConfigLoad and NetConfigStore take a handle from prefs.bas (add prefs.bas to the project too). NetConfigLoad gives a settings handle (0 when none is free); read and change its fields by name, check them, store them, and restart the network only when they changed:
|
||||||
|
|
||||||
.code
|
.code
|
||||||
|
|
@ -6198,6 +6374,7 @@ End Sub
|
||||||
NetVectorCount() / NetVector(index) The packet driver interrupts to choose from: index 0 is 0 (find it), then each one the settings accept.
|
NetVectorCount() / NetVector(index) The packet driver interrupts to choose from: index 0 is 0 (find it), then each one the settings accept.
|
||||||
NetRestart() Stop the network and start it with the saved settings. True when it is running.
|
NetRestart() Stop the network and start it with the saved settings. True when it is running.
|
||||||
NetStop Stop the network (every program's connections are closed) until NetRestart.
|
NetStop Stop the network (every program's connections are closed) until NetRestart.
|
||||||
|
NetState() The network's state as a number: NET_STATE_STOPPED, NET_STATE_NO_DRIVER (no network card driver found), NET_STATE_CONFIGURING (waiting for an address from the network), or NET_STATE_UP.
|
||||||
NetStatus$(field) The network's state as text: "message" (in plain words), "up" and "link" ("-1" / "0"), "dhcp", "address", "netmask", "gateway", "dns1", "dns2", "hostname", "driver" (the network card's driver), "mac" (its hardware address), "vector", "sockets", "rxpackets", "rxbytes", "rxdropped", "txpackets", "txbytes". "" for an unknown field.
|
NetStatus$(field) The network's state as text: "message" (in plain words), "up" and "link" ("-1" / "0"), "dhcp", "address", "netmask", "gateway", "dns1", "dns2", "hostname", "driver" (the network card's driver), "mac" (its hardware address), "vector", "sockets", "rxpackets", "rxbytes", "rxdropped", "txpackets", "txbytes". "" for an unknown field.
|
||||||
.endtable
|
.endtable
|
||||||
|
|
||||||
|
|
@ -6239,7 +6416,7 @@ End Sub
|
||||||
|
|
||||||
.h2 File Transfers
|
.h2 File Transfers
|
||||||
|
|
||||||
With xfer.bas in the project, a program sends and receives files with XMODEM, XMODEM-1K, YMODEM, or ZMODEM. A transfer is a number: XferSendFile and XferReceive start one on a serial port opened with SerOpen (comm.bas), and ConnSendFiles and ConnReceiveFiles start one on a connection (conn.bas), with the options Terminal has: escaping every control character, asking the receiver to continue interrupted files, and what to do with a file that is already there. On a serial port XferPoll moves the transfer along; on a connection ConnPoll does. XferStatus tells where it is (XFER_RUNNING until it ends with XFER_DONE, XFER_FAILED, or XFER_CANCELLED), and XferBytes, XferSize, XferFile$, XferFileIndex, XferFileCount, XferBatchBytes, XferBatchSize, XferResumedAt, and XferErrors show its progress; XferMessage$ says how it ended. A receive with XFER_EXIST_ASK stops in XFER_ASKING when a file already exists: XferAskSize, XferAskRemoteSize (the size the sender offers, -1 when it did not say), and XferCanResume describe it, and XferAnswer continues with an XFER_EXIST_ choice. XferCancel stops a transfer and XferClose frees it.
|
With xfer.bas in the project, a program sends and receives files with XMODEM, XMODEM-1K, YMODEM, or ZMODEM. A transfer is a number: XferSendFile and XferReceive start one on a serial port opened with SerOpen (comm.bas), and ConnSendFiles and ConnReceiveFiles start one on a connection (conn.bas). XferSendFiles and XferReceiveFiles (a serial port) and the Conn ones take the options Terminal has: a batch of files (one path per line), escaping every control character, asking the receiver to continue interrupted files, and what to do with a file that is already there. On a serial port XferPoll moves the transfer along; on a connection ConnPoll does. XferStatus tells where it is (XFER_RUNNING until it ends with XFER_DONE, XFER_FAILED, or XFER_CANCELLED), and XferBytes, XferSize, XferFile$, XferFileIndex, XferFileCount, XferBatchBytes, XferBatchSize, XferResumedAt, and XferErrors show its progress; XferMessage$ says how it ended. A receive with XFER_EXIST_ASK stops in XFER_ASKING when a file already exists: XferAskSize, XferAskRemoteSize (the size the sender offers, -1 when it did not say), and XferCanResume describe it, and XferAnswer continues with an XFER_EXIST_ choice. XferCancel stops a transfer and XferClose frees it.
|
||||||
|
|
||||||
XferCrc16(bytes) and XferCrc32(bytes) give the check values the protocols use (CRC-16 as XMODEM computes it, 0 to 65535; the CRC-32 of ZMODEM and ZIP files as a Long, negative when its top bit is set) for any bytes. XferSafeName$(name, folder) gives the name a receive into folder stores a sender's file name as: no folder or device name; a long name, its case kept, where folder's disk takes long names (a character a name cannot hold becomes _), else an 8.3 name of characters every disk accepts.
|
XferCrc16(bytes) and XferCrc32(bytes) give the check values the protocols use (CRC-16 as XMODEM computes it, 0 to 65535; the CRC-32 of ZMODEM and ZIP files as a Long, negative when its top bit is set) for any bytes. XferSafeName$(name, folder) gives the name a receive into folder stores a sender's file name as: no folder or device name; a long name, its case kept, where folder's disk takes long names (a character a name cannot hold becomes _), else an 8.3 name of characters every disk accepts.
|
||||||
|
|
||||||
|
|
@ -6321,11 +6498,20 @@ With security.bas in the project, a program has the building blocks of DVX's enc
|
||||||
Function Description
|
Function Description
|
||||||
-------- -----------
|
-------- -----------
|
||||||
SecRandom$(count) count random bytes, good for keys and salts (RND is not). SecAddEntropy bytes stirs something unpredictable (key timings, mouse positions) into the generator.
|
SecRandom$(count) count random bytes, good for keys and salts (RND is not). SecAddEntropy bytes stirs something unpredictable (key timings, mouse positions) into the generator.
|
||||||
SecDhCreate() A key exchange with fresh keys (a few seconds on a slow computer); a handle, or 0.
|
SecX25519PublicKey$(secretKey) The public key to send the other side, for a secret key of SEC_X25519_KEY_SIZE random bytes (SecRandom$).
|
||||||
|
SecX25519$(secretKey, otherPublicKey) The secret both sides now share, which nobody listening can work out ("" for a key the other side should not have sent). About a sixth of a second on a slow computer. Make keys from it with SecHkdf$.
|
||||||
|
SecX25519Start(secretKey, otherPublicKey) The same without the wait: SecX25519Step(h) then does a ninth of the work a call, returning SEC_X25519_PENDING until it is done (SEC_OK); SecX25519End$(h) gives the secret (with otherPublicKey "", the public key) and frees the handle.
|
||||||
|
SecAeadLock$(key, nonce, extra, bytes) bytes sealed: encrypted with a SEC_AEAD_KEY_SIZE-byte key and a SEC_AEAD_NONCE_SIZE-byte nonce, followed by a tag that proves them, and extra (sent in the clear), unchanged. Use a new nonce for every message: SecRandom$(SEC_AEAD_NONCE_SIZE) is safe.
|
||||||
|
SecAeadUnlock$(key, nonce, extra, sealed) What SecAeadLock$ sealed, or "" when anything was changed (or the key, nonce, or extra differs).
|
||||||
|
SecEd25519KeyPair$(seed) A signing key made from SecRandom$(SEC_ED25519_SEED_SIZE); keep it private. SecEd25519PublicKey$(key) is the key to give out.
|
||||||
|
SecEd25519Sign$(key, bytes) The signature of bytes; SecEd25519Check(signature, publicKey, bytes) is True only for exactly those bytes signed with that key.
|
||||||
|
SecDhCreate() The older key exchange (Diffie-Hellman), with fresh keys (a few seconds on a slow computer); a handle, or 0.
|
||||||
|
SecDhCreateStart() The same without the wait: SecDhStep(dh) then computes the keys a slice at a time, returning SEC_DH_PENDING until they are ready (SEC_OK).
|
||||||
SecDhPublicKey$(dh) This side's public key (SEC_DH_KEY_SIZE bytes), to send to the other side.
|
SecDhPublicKey$(dh) This side's public key (SEC_DH_KEY_SIZE bytes), to send to the other side.
|
||||||
SecDhComputeSecret(dh, otherPublicKey) Combine with the other side's public key: SEC_OK, or SEC_ERR_PARAM for a key that is not acceptable.
|
SecDhComputeSecret(dh, otherPublicKey) Combine with the other side's public key: SEC_OK, or SEC_ERR_PARAM for a key that is not acceptable.
|
||||||
|
SecDhComputeStart(dh, otherPublicKey) The same a slice at a time: SEC_DH_PENDING (or SEC_ERR_PARAM at once), then SecDhStep as above.
|
||||||
SecDhSecret$(dh) The secret both sides now share, which nobody listening can work out. Make keys from it with SecHkdf$; never use it as a key itself. SecDhFree dh frees the handle.
|
SecDhSecret$(dh) The secret both sides now share, which nobody listening can work out. Make keys from it with SecHkdf$; never use it as a key itself. SecDhFree dh frees the handle.
|
||||||
SecCipherCreate(key) A cipher with a SEC_XTEA_KEY_SIZE-byte key; 0 for a key of another length.
|
SecCipherCreate(key) The older cipher (XTEA), with a SEC_XTEA_KEY_SIZE-byte key; 0 for a key of another length. It does not show tampering: add SecHmac$, or seal instead.
|
||||||
SecCipherCrypt$(cipher, bytes) Encrypt -- or decrypt, the same call -- bytes. SecCipherSetCounter cipher, n sets the position in the key stream; the same key and position must never encrypt two different messages. SecCipherFree cipher frees it.
|
SecCipherCrypt$(cipher, bytes) Encrypt -- or decrypt, the same call -- bytes. SecCipherSetCounter cipher, n sets the position in the key stream; the same key and position must never encrypt two different messages. SecCipherFree cipher frees it.
|
||||||
SecSha256$(bytes) The SHA-256 hash (SEC_SHA256_SIZE bytes). For data in pieces: SecSha256Open, SecSha256Add h, piece, then SecSha256End$(h), which also frees the handle.
|
SecSha256$(bytes) The SHA-256 hash (SEC_SHA256_SIZE bytes). For data in pieces: SecSha256Open, SecSha256Add h, piece, then SecSha256End$(h), which also frees the handle.
|
||||||
SecHmac$(key, bytes) The HMAC-SHA256 signature of bytes with key; SecHmacOpen(key), SecHmacAdd, and SecHmacEnd$ for pieces.
|
SecHmac$(key, bytes) The HMAC-SHA256 signature of bytes with key; SecHmacOpen(key), SecHmacAdd, and SecHmacEnd$ for pieces.
|
||||||
|
|
@ -6334,16 +6520,15 @@ With security.bas in the project, a program has the building blocks of DVX's enc
|
||||||
.endtable
|
.endtable
|
||||||
|
|
||||||
.code
|
.code
|
||||||
' Both sides: make keys, swap public keys (here over a TCP connection), agree a cipher key
|
' Both sides: make keys, swap public keys (here over a TCP connection), agree a sealing key
|
||||||
Dim dh As Long, cipher As Long, key As String
|
Dim mine As String, theirs As String, key As String, nonce As String
|
||||||
dh = SecDhCreate()
|
mine = SecRandom$(SEC_X25519_KEY_SIZE)
|
||||||
TcpSend conn, SecDhPublicKey$(dh)
|
TcpSend conn, SecX25519PublicKey$(mine)
|
||||||
' ... receive the other side's key into theirs ...
|
' ... receive the other side's public key into theirs ...
|
||||||
If SecDhComputeSecret(dh, theirs) = SEC_OK Then
|
key = SecHkdf$("", SecX25519$(mine, theirs), "chat", SEC_AEAD_KEY_SIZE)
|
||||||
key = SecHkdf$("", SecDhSecret$(dh), "chat cipher", SEC_XTEA_KEY_SIZE)
|
' Send a message: the nonce, then the sealed bytes
|
||||||
cipher = SecCipherCreate(key)
|
nonce = SecRandom$(SEC_AEAD_NONCE_SIZE)
|
||||||
End If
|
TcpSend conn, nonce & SecAeadLock$(key, nonce, "", "Hello")
|
||||||
SecDhFree dh
|
|
||||||
.endcode
|
.endcode
|
||||||
|
|
||||||
Handles a program leaves open are freed, and their keys wiped, when it ends.
|
Handles a program leaves open are freed, and their keys wiped, when it ends.
|
||||||
|
|
@ -6376,7 +6561,7 @@ cboAccel.ListIndex = PrefsChoiceIndexOfValue(PREFS_CHOICE_ACCEL, SysMouseAccel()
|
||||||
|
|
||||||
.h2 Help Files
|
.h2 Help Files
|
||||||
|
|
||||||
With help.bas in the project, HelpCompile(source, output) compiles a help source file (.dhs) into a help file (.hlp), True when it worked; HelpCompileMessages$() gives the compiler's errors and warnings, one per line, each with the file and line ("error: NOTES.DHS:1: directive .h1 outside of a topic"). HelpView file opens a help file in the DVX Help Viewer (a file named without a folder is looked for beside the program; "" opens the DVX user guide). HelpViewTopic file, topicId opens it at a topic, the way F1 does. A Help Viewer that is already open shows the file in its window instead of a second copy starting.
|
With help.bas in the project, HelpCompile(source, output) compiles a help source file (.dhs) into a help file (.hlp), True when it worked; its .image pictures are looked for in the source's folder. HelpCompileMessages$() gives the compiler's errors and warnings, one per line, each with the file and line ("error: NOTES.DHS:1: directive .h1 outside of a topic"). While a big file compiles, the rest of DVX and the program's own events go on, as with DoEvents: one compile runs at a time, so a second HelpCompile meanwhile returns False at once, and the compile stops if the program ends. HelpView file opens a help file in the DVX Help Viewer (a file named without a folder is looked for beside the program; "" opens the DVX user guide). HelpViewTopic file, topicId opens it at a topic, the way F1 does. A Help Viewer that is already open shows the file in its window instead of a second copy starting.
|
||||||
|
|
||||||
With hlpfile.bas, a program reads help files itself -- everything a help viewer needs. HlpOpen(path) opens a help file and returns a handle (0 when it is not one); HlpClose closes it (a program's open files are closed when it ends). Topics are numbered from 0: HlpTopicCount, HlpTopicTitle$ and HlpTopicId$ (the name links and HelpTopic use), HlpFindTopic(handle, topicId) (-1 when there is none), and HlpStartTopic(handle, topicId), the topic to show first (topicId when the file has it, else the file's own start page). HlpTocCount, HlpTocTitle$, HlpTocDepth, HlpTocTopic, and HlpTocExpanded give the table of contents in order -- a depth of 0 is the top level, a topic of -1 a heading that only groups others -- ready to fill a TreeView. HlpIndexCount, HlpIndexKeyword$, and HlpIndexTopic give the keyword index (sorted), for a list or DlgChoice. HlpSearch(handle, words) searches the titles, text, and keywords as the Help Viewer does and returns how many topics have every word; HlpSearchTopic(handle, n) gives them.
|
With hlpfile.bas, a program reads help files itself -- everything a help viewer needs. HlpOpen(path) opens a help file and returns a handle (0 when it is not one); HlpClose closes it (a program's open files are closed when it ends). Topics are numbered from 0: HlpTopicCount, HlpTopicTitle$ and HlpTopicId$ (the name links and HelpTopic use), HlpFindTopic(handle, topicId) (-1 when there is none), and HlpStartTopic(handle, topicId), the topic to show first (topicId when the file has it, else the file's own start page). HlpTocCount, HlpTocTitle$, HlpTocDepth, HlpTocTopic, and HlpTocExpanded give the table of contents in order -- a depth of 0 is the top level, a topic of -1 a heading that only groups others -- ready to fill a TreeView. HlpIndexCount, HlpIndexKeyword$, and HlpIndexTopic give the keyword index (sorted), for a list or DlgChoice. HlpSearch(handle, words) searches the titles, text, and keywords as the Help Viewer does and returns how many topics have every word; HlpSearchTopic(handle, n) gives them.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,7 @@
|
||||||
#define BAS_ERR_DIV_BY_ZERO 11
|
#define BAS_ERR_DIV_BY_ZERO 11
|
||||||
#define BAS_ERR_TYPE_MISMATCH 13
|
#define BAS_ERR_TYPE_MISMATCH 13
|
||||||
#define BAS_ERR_OUT_OF_STRING 14
|
#define BAS_ERR_OUT_OF_STRING 14
|
||||||
|
#define BAS_ERR_INTERRUPTED 18 // User interrupt occurred: code stopped for running too long (basVmSetRunLimit)
|
||||||
#define BAS_ERR_RESUME_WITHOUT_ERR 20
|
#define BAS_ERR_RESUME_WITHOUT_ERR 20
|
||||||
#define BAS_ERR_FOR_NESTING 26
|
#define BAS_ERR_FOR_NESTING 26
|
||||||
#define BAS_ERR_OUT_OF_STACK 28
|
#define BAS_ERR_OUT_OF_STACK 28
|
||||||
|
|
|
||||||
|
|
@ -70,6 +70,8 @@ typedef struct {
|
||||||
uint32_t border; // pixel the fill stops at
|
uint32_t border; // pixel the fill stops at
|
||||||
const BasGfxTileT *tile; // NULL: colour fill
|
const BasGfxTileT *tile; // NULL: colour fill
|
||||||
uint8_t *done; // tile fill: one bit per view pixel
|
uint8_t *done; // tile fill: one bit per view pixel
|
||||||
|
uint32_t *row; // a row of the view read at once (rowRead)
|
||||||
|
uint32_t *near; // and the run above or below it
|
||||||
} GfxFloodT;
|
} GfxFloodT;
|
||||||
|
|
||||||
// An ellipse outline (CIRCLE): its centre and radii in pixels, the arc
|
// An ellipse outline (CIRCLE): its centre and radii in pixels, the arc
|
||||||
|
|
@ -133,7 +135,7 @@ static int32_t drawNumber(const char *s, int32_t len, int32_t *pos, bool allowS
|
||||||
static void ellipse(BasGfxT *g, const GfxEllipseT *e);
|
static void ellipse(BasGfxT *g, const GfxEllipseT *e);
|
||||||
static void ellipseFill(BasGfxT *g, int32_t cx, int32_t cy, int32_t rx, int32_t ry, uint32_t pixel);
|
static void ellipseFill(BasGfxT *g, int32_t cx, int32_t cy, int32_t rx, int32_t ry, uint32_t pixel);
|
||||||
static void ellipsePoint(BasGfxT *g, const GfxEllipseT *e, int32_t dx, int32_t dy);
|
static void ellipsePoint(BasGfxT *g, const GfxEllipseT *e, int32_t dx, int32_t dy);
|
||||||
static bool fillable(const GfxFloodT *f, int32_t x, int32_t y);
|
static bool fillable(const GfxFloodT *f, uint32_t pixel, int32_t x, int32_t y);
|
||||||
static void fillRect(BasGfxT *g, int32_t x0, int32_t y0, int32_t x1, int32_t y1, uint32_t pixel, int32_t style);
|
static void fillRect(BasGfxT *g, int32_t x0, int32_t y0, int32_t x1, int32_t y1, uint32_t pixel, int32_t style);
|
||||||
static void fillSpan(BasGfxT *g, int32_t y, int32_t x0, int32_t x1, uint32_t pixel, int32_t style);
|
static void fillSpan(BasGfxT *g, int32_t y, int32_t x0, int32_t x1, uint32_t pixel, int32_t style);
|
||||||
static int32_t floodFill(BasGfxT *g, int32_t x, int32_t y, uint32_t paint, uint32_t border, const BasGfxTileT *tile);
|
static int32_t floodFill(BasGfxT *g, int32_t x, int32_t y, uint32_t paint, uint32_t border, const BasGfxTileT *tile);
|
||||||
|
|
@ -151,6 +153,9 @@ static int32_t penSize(const BasGfxT *g);
|
||||||
static void plot(BasGfxT *g, int32_t x, int32_t y, uint32_t pixel);
|
static void plot(BasGfxT *g, int32_t x, int32_t y, uint32_t pixel);
|
||||||
static void plotPixel(BasGfxT *g, int32_t x, int32_t y, uint32_t pixel);
|
static void plotPixel(BasGfxT *g, int32_t x, int32_t y, uint32_t pixel);
|
||||||
static void pointArg(const BasGfxT *g, BasValueT x, BasValueT y, bool step, double *outX, double *outY);
|
static void pointArg(const BasGfxT *g, BasValueT x, BasValueT y, bool step, double *outX, double *outY);
|
||||||
|
static void rowRead(const BasGfxT *g, int32_t x0, int32_t x1, int32_t y, uint32_t *out);
|
||||||
|
static int32_t rowsBottom(const BasGfxT *g);
|
||||||
|
static int32_t rowsTop(const BasGfxT *g);
|
||||||
static double scaleLen(const BasGfxT *g, double len);
|
static double scaleLen(const BasGfxT *g, double len);
|
||||||
static bool shapeFilled(const BasGfxT *g);
|
static bool shapeFilled(const BasGfxT *g);
|
||||||
static double spanX(const BasGfxT *g);
|
static double spanX(const BasGfxT *g);
|
||||||
|
|
@ -335,6 +340,7 @@ void basGfxInit(BasGfxT *g, const BasGfxSurfaceT *surf, uint32_t fore, uint32_t
|
||||||
g->surf = *surf;
|
g->surf = *surf;
|
||||||
g->viewX1 = surf->width - 1;
|
g->viewX1 = surf->width - 1;
|
||||||
g->viewY1 = surf->height - 1;
|
g->viewY1 = surf->height - 1;
|
||||||
|
g->bandY1 = BAS_GFX_BAND_ALL;
|
||||||
g->fore = fore;
|
g->fore = fore;
|
||||||
g->back = back;
|
g->back = back;
|
||||||
g->fillStyle = BAS_GFX_FILL_TRANSPARENT;
|
g->fillStyle = BAS_GFX_FILL_TRANSPARENT;
|
||||||
|
|
@ -1174,9 +1180,8 @@ static void ellipsePoint(BasGfxT *g, const GfxEllipseT *e, int32_t dx, int32_t d
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static bool fillable(const GfxFloodT *f, int32_t x, int32_t y) {
|
// Whether the flood takes the pixel at (x, y), whose value is pixel.
|
||||||
uint32_t pixel = f->g->surf.get(f->g->surf.ctx, x, y);
|
static bool fillable(const GfxFloodT *f, uint32_t pixel, int32_t x, int32_t y) {
|
||||||
|
|
||||||
if (pixel == f->border) {
|
if (pixel == f->border) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -1202,8 +1207,8 @@ static void fillRect(BasGfxT *g, int32_t x0, int32_t y0, int32_t x1, int32_t y1,
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
top = top < g->viewY0 ? g->viewY0 : top;
|
top = top < rowsTop(g) ? rowsTop(g) : top;
|
||||||
bottom = bottom > g->viewY1 ? g->viewY1 : bottom;
|
bottom = bottom > rowsBottom(g) ? rowsBottom(g) : bottom;
|
||||||
|
|
||||||
for (int32_t y = top; y <= bottom; y++) {
|
for (int32_t y = top; y <= bottom; y++) {
|
||||||
fillSpan(g, y, x0, x1, pixel, style);
|
fillSpan(g, y, x0, x1, pixel, style);
|
||||||
|
|
@ -1222,7 +1227,7 @@ static void fillSpan(BasGfxT *g, int32_t y, int32_t x0, int32_t x1, uint32_t pix
|
||||||
int32_t left = x0 < x1 ? x0 : x1;
|
int32_t left = x0 < x1 ? x0 : x1;
|
||||||
int32_t right = x0 < x1 ? x1 : x0;
|
int32_t right = x0 < x1 ? x1 : x0;
|
||||||
|
|
||||||
if (y < g->viewY0 || y > g->viewY1) {
|
if (y < rowsTop(g) || y > rowsBottom(g)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1239,8 +1244,10 @@ static void fillSpan(BasGfxT *g, int32_t y, int32_t x0, int32_t x1, uint32_t pix
|
||||||
|
|
||||||
// Scan-line flood fill inside the view: everything reachable from (x, y)
|
// Scan-line flood fill inside the view: everything reachable from (x, y)
|
||||||
// that is not the border colour (nor, for a colour, already that colour).
|
// that is not the border colour (nor, for a colour, already that colour).
|
||||||
|
// Rows are read a run at a time (rowRead), not a pixel at a time.
|
||||||
static int32_t floodFill(BasGfxT *g, int32_t x, int32_t y, uint32_t paint, uint32_t border, const BasGfxTileT *tile) {
|
static int32_t floodFill(BasGfxT *g, int32_t x, int32_t y, uint32_t paint, uint32_t border, const BasGfxTileT *tile) {
|
||||||
GfxSeedT *seeds = NULL;
|
GfxSeedT *seeds = NULL;
|
||||||
|
int32_t width = g->viewX1 - g->viewX0 + 1;
|
||||||
GfxFloodT f;
|
GfxFloodT f;
|
||||||
|
|
||||||
f.g = g;
|
f.g = g;
|
||||||
|
|
@ -1248,15 +1255,20 @@ static int32_t floodFill(BasGfxT *g, int32_t x, int32_t y, uint32_t paint, uint3
|
||||||
f.border = border;
|
f.border = border;
|
||||||
f.tile = tile;
|
f.tile = tile;
|
||||||
f.done = NULL;
|
f.done = NULL;
|
||||||
|
f.row = (uint32_t *)malloc((size_t)width * sizeof(uint32_t));
|
||||||
|
f.near = (uint32_t *)malloc((size_t)width * sizeof(uint32_t));
|
||||||
|
|
||||||
if (tile) {
|
if (tile) {
|
||||||
size_t pixels = (size_t)(g->viewX1 - g->viewX0 + 1) * (size_t)(g->viewY1 - g->viewY0 + 1);
|
size_t pixels = (size_t)width * (size_t)(g->viewY1 - g->viewY0 + 1);
|
||||||
|
|
||||||
f.done = (uint8_t *)calloc(pixels / GFX_BITS_PER_BYTE + 1, 1);
|
f.done = (uint8_t *)calloc(pixels / GFX_BITS_PER_BYTE + 1, 1);
|
||||||
|
|
||||||
if (!f.done) {
|
|
||||||
return BAS_ERR_OUT_OF_MEMORY;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!f.row || !f.near || (tile && !f.done)) {
|
||||||
|
free(f.row);
|
||||||
|
free(f.near);
|
||||||
|
free(f.done);
|
||||||
|
return BAS_ERR_OUT_OF_MEMORY;
|
||||||
}
|
}
|
||||||
|
|
||||||
arrput(seeds, ((GfxSeedT){ x, y }));
|
arrput(seeds, ((GfxSeedT){ x, y }));
|
||||||
|
|
@ -1264,18 +1276,20 @@ static int32_t floodFill(BasGfxT *g, int32_t x, int32_t y, uint32_t paint, uint3
|
||||||
while (arrlen(seeds) > 0) {
|
while (arrlen(seeds) > 0) {
|
||||||
GfxSeedT s = arrpop(seeds);
|
GfxSeedT s = arrpop(seeds);
|
||||||
|
|
||||||
if (!fillable(&f, s.x, s.y)) {
|
rowRead(g, g->viewX0, g->viewX1, s.y, f.row);
|
||||||
|
|
||||||
|
if (!fillable(&f, f.row[s.x - g->viewX0], s.x, s.y)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
int32_t left = s.x;
|
int32_t left = s.x;
|
||||||
int32_t right = s.x;
|
int32_t right = s.x;
|
||||||
|
|
||||||
while (left > g->viewX0 && fillable(&f, left - 1, s.y)) {
|
while (left > g->viewX0 && fillable(&f, f.row[left - 1 - g->viewX0], left - 1, s.y)) {
|
||||||
left--;
|
left--;
|
||||||
}
|
}
|
||||||
|
|
||||||
while (right < g->viewX1 && fillable(&f, right + 1, s.y)) {
|
while (right < g->viewX1 && fillable(&f, f.row[right + 1 - g->viewX0], right + 1, s.y)) {
|
||||||
right++;
|
right++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1288,8 +1302,10 @@ static int32_t floodFill(BasGfxT *g, int32_t x, int32_t y, uint32_t paint, uint3
|
||||||
|
|
||||||
bool inRun = false;
|
bool inRun = false;
|
||||||
|
|
||||||
|
rowRead(g, left, right, ny, f.near);
|
||||||
|
|
||||||
for (int32_t nx = left; nx <= right; nx++) {
|
for (int32_t nx = left; nx <= right; nx++) {
|
||||||
bool can = fillable(&f, nx, ny);
|
bool can = fillable(&f, f.near[nx - left], nx, ny);
|
||||||
|
|
||||||
if (can && !inRun) {
|
if (can && !inRun) {
|
||||||
arrput(seeds, ((GfxSeedT){ nx, ny }));
|
arrput(seeds, ((GfxSeedT){ nx, ny }));
|
||||||
|
|
@ -1301,6 +1317,8 @@ static int32_t floodFill(BasGfxT *g, int32_t x, int32_t y, uint32_t paint, uint3
|
||||||
}
|
}
|
||||||
|
|
||||||
arrfree(seeds);
|
arrfree(seeds);
|
||||||
|
free(f.row);
|
||||||
|
free(f.near);
|
||||||
free(f.done);
|
free(f.done);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
@ -1361,12 +1379,12 @@ static bool hatchOn(int32_t style, int32_t x, int32_t y) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// A horizontal span clipped to the view.
|
// A horizontal span clipped to the view (and band).
|
||||||
static void hspan(BasGfxT *g, int32_t y, int32_t x0, int32_t x1, uint32_t pixel) {
|
static void hspan(BasGfxT *g, int32_t y, int32_t x0, int32_t x1, uint32_t pixel) {
|
||||||
int32_t left = x0 < x1 ? x0 : x1;
|
int32_t left = x0 < x1 ? x0 : x1;
|
||||||
int32_t right = x0 < x1 ? x1 : x0;
|
int32_t right = x0 < x1 ? x1 : x0;
|
||||||
|
|
||||||
if (y < g->viewY0 || y > g->viewY1) {
|
if (y < rowsTop(g) || y > rowsBottom(g)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1380,7 +1398,7 @@ static void hspan(BasGfxT *g, int32_t y, int32_t x0, int32_t x1, uint32_t pixel)
|
||||||
|
|
||||||
|
|
||||||
static bool inView(const BasGfxT *g, int32_t x, int32_t y) {
|
static bool inView(const BasGfxT *g, int32_t x, int32_t y) {
|
||||||
return x >= g->viewX0 && x <= g->viewX1 && y >= g->viewY0 && y <= g->viewY1;
|
return x >= g->viewX0 && x <= g->viewX1 && y >= rowsTop(g) && y <= rowsBottom(g);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1507,7 +1525,11 @@ static void plot(BasGfxT *g, int32_t x, int32_t y, uint32_t pixel) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int32_t dy = -r; dy <= r; dy++) {
|
// Only the disc's rows that are drawn (a band of a page: most are not)
|
||||||
|
int32_t from = y - r < rowsTop(g) ? rowsTop(g) - y : -r;
|
||||||
|
int32_t to = y + r > rowsBottom(g) ? rowsBottom(g) - y : r;
|
||||||
|
|
||||||
|
for (int32_t dy = from; dy <= to; dy++) {
|
||||||
int32_t half = basGfxRound(sqrt((double)(r * r - dy * dy)));
|
int32_t half = basGfxRound(sqrt((double)(r * r - dy * dy)));
|
||||||
|
|
||||||
hspan(g, y + dy, x - half, x + half, pixel);
|
hspan(g, y + dy, x - half, x + half, pixel);
|
||||||
|
|
@ -1529,6 +1551,32 @@ static void pointArg(const BasGfxT *g, BasValueT x, BasValueT y, bool step, doub
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Pixels x0..x1 of row y into out: the surface's row read, or a pixel at
|
||||||
|
// a time.
|
||||||
|
static void rowRead(const BasGfxT *g, int32_t x0, int32_t x1, int32_t y, uint32_t *out) {
|
||||||
|
if (g->surf.getRow) {
|
||||||
|
g->surf.getRow(g->surf.ctx, x0, x1, y, out);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int32_t x = x0; x <= x1; x++) {
|
||||||
|
out[x - x0] = g->surf.get(g->surf.ctx, x, y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// The last row drawn: the view's, or the band's above it.
|
||||||
|
static int32_t rowsBottom(const BasGfxT *g) {
|
||||||
|
return g->viewY1 < g->bandY1 ? g->viewY1 : g->bandY1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// The first row drawn: the view's, or the band's below it.
|
||||||
|
static int32_t rowsTop(const BasGfxT *g) {
|
||||||
|
return g->viewY0 > g->bandY0 ? g->viewY0 : g->bandY0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// A length in logical x units -> pixels.
|
// A length in logical x units -> pixels.
|
||||||
static double scaleLen(const BasGfxT *g, double len) {
|
static double scaleLen(const BasGfxT *g, double len) {
|
||||||
if (!g->world) {
|
if (!g->world) {
|
||||||
|
|
|
||||||
|
|
@ -107,6 +107,7 @@ typedef struct {
|
||||||
uint32_t colorMax; // largest colour number it takes (0: any value, e.g. RGB)
|
uint32_t colorMax; // largest colour number it takes (0: any value, e.g. RGB)
|
||||||
void (*span)(void *ctx, int32_t x0, int32_t x1, int32_t y, uint32_t pixel);
|
void (*span)(void *ctx, int32_t x0, int32_t x1, int32_t y, uint32_t pixel);
|
||||||
uint32_t (*get)(const void *ctx, int32_t x, int32_t y);
|
uint32_t (*get)(const void *ctx, int32_t x, int32_t y);
|
||||||
|
void (*getRow)(const void *ctx, int32_t x0, int32_t x1, int32_t y, uint32_t *out); // pixels x0..x1 of row y (NULL: get a pixel at a time)
|
||||||
uint32_t (*toPixel)(void *ctx, uint32_t color); // NULL: colours are pixels
|
uint32_t (*toPixel)(void *ctx, uint32_t color); // NULL: colours are pixels
|
||||||
uint32_t (*toColor)(const void *ctx, uint32_t pixel); // NULL: pixels are colours
|
uint32_t (*toColor)(const void *ctx, uint32_t pixel); // NULL: pixels are colours
|
||||||
const BasGfxVectorT *vec; // NULL: shapes are spans
|
const BasGfxVectorT *vec; // NULL: shapes are spans
|
||||||
|
|
@ -120,6 +121,9 @@ typedef struct {
|
||||||
uint32_t colors[BAS_GFX_TILE_MAX_H][BAS_GFX_TILE_MAX_W];
|
uint32_t colors[BAS_GFX_TILE_MAX_H][BAS_GFX_TILE_MAX_W];
|
||||||
} BasGfxTileT;
|
} BasGfxTileT;
|
||||||
|
|
||||||
|
// BasGfxT.bandY1 when no band limits the rows drawn
|
||||||
|
#define BAS_GFX_BAND_ALL INT32_MAX
|
||||||
|
|
||||||
// Drawing state of one surface.
|
// Drawing state of one surface.
|
||||||
typedef struct {
|
typedef struct {
|
||||||
BasGfxSurfaceT surf;
|
BasGfxSurfaceT surf;
|
||||||
|
|
@ -127,6 +131,8 @@ typedef struct {
|
||||||
int32_t viewY0;
|
int32_t viewY0;
|
||||||
int32_t viewX1;
|
int32_t viewX1;
|
||||||
int32_t viewY1;
|
int32_t viewY1;
|
||||||
|
int32_t bandY0; // rows drawn at all, with the view's: a printed page's band being drawn; 0..BAS_GFX_BAND_ALL otherwise
|
||||||
|
int32_t bandY1;
|
||||||
bool viewAbsolute; // VIEW SCREEN: coordinates are not moved to the view's corner
|
bool viewAbsolute; // VIEW SCREEN: coordinates are not moved to the view's corner
|
||||||
bool world; // WINDOW or a VB Scale is on: logical units are mapped onto the view
|
bool world; // WINDOW or a VB Scale is on: logical units are mapped onto the view
|
||||||
bool worldEdges; // VB Scale: the width covers the view's pixels edge to edge (WINDOW: first to last pixel centre)
|
bool worldEdges; // VB Scale: the width covers the view's pixels edge to edge (WINDOW: first to last pixel centre)
|
||||||
|
|
|
||||||
|
|
@ -286,6 +286,7 @@ static BasScreenT *screenEnsure(BasVmT *vm);
|
||||||
static void scrollUp(BasScreenT *scr);
|
static void scrollUp(BasScreenT *scr);
|
||||||
static int32_t setMode(BasScreenT *scr, int32_t mode, int32_t cols, int32_t rows);
|
static int32_t setMode(BasScreenT *scr, int32_t mode, int32_t cols, int32_t rows);
|
||||||
static uint32_t surfGet(const void *ctx, int32_t x, int32_t y);
|
static uint32_t surfGet(const void *ctx, int32_t x, int32_t y);
|
||||||
|
static void surfGetRow(const void *ctx, int32_t x0, int32_t x1, int32_t y, uint32_t *out);
|
||||||
static void surfSpan(void *ctx, int32_t x0, int32_t x1, int32_t y, uint32_t pixel);
|
static void surfSpan(void *ctx, int32_t x0, int32_t x1, int32_t y, uint32_t pixel);
|
||||||
static uint8_t textBackPixel(const BasScreenT *scr);
|
static uint8_t textBackPixel(const BasScreenT *scr);
|
||||||
static void textRedraw(BasScreenT *scr, bool blinkOnly);
|
static void textRedraw(BasScreenT *scr, bool blinkOnly);
|
||||||
|
|
@ -340,7 +341,7 @@ int32_t basScreenExec(BasVmT *vm, uint8_t fn, BasValueT *args, int32_t argc, Bas
|
||||||
*result = basValInteger(0);
|
*result = basValInteger(0);
|
||||||
|
|
||||||
// LINE INPUT and POS work without a screen (the host's input and
|
// LINE INPUT and POS work without a screen (the host's input and
|
||||||
// output); everything else makes one.
|
// output); everything else but FULLSCREEN's state makes one.
|
||||||
if (fn == BAS_FN_LINE_INPUT) {
|
if (fn == BAS_FN_LINE_INPUT) {
|
||||||
return execLineInput(vm, args[0], result);
|
return execLineInput(vm, args[0], result);
|
||||||
}
|
}
|
||||||
|
|
@ -350,6 +351,12 @@ int32_t basScreenExec(BasVmT *vm, uint8_t fn, BasValueT *args, int32_t argc, Bas
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FULLSCREEN's state makes no screen either
|
||||||
|
if (fn == BAS_FN_FULLSCREEN_STATE) {
|
||||||
|
*result = basValBool(vm->screen && vm->screen->fullScreen);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
BasScreenT *scr = screenEnsure(vm);
|
BasScreenT *scr = screenEnsure(vm);
|
||||||
|
|
||||||
if (!scr) {
|
if (!scr) {
|
||||||
|
|
@ -1563,6 +1570,7 @@ static int32_t setMode(BasScreenT *scr, int32_t mode, int32_t cols, int32_t rows
|
||||||
surf.colorMax = (uint32_t)info->colors - 1;
|
surf.colorMax = (uint32_t)info->colors - 1;
|
||||||
surf.span = surfSpan;
|
surf.span = surfSpan;
|
||||||
surf.get = surfGet;
|
surf.get = surfGet;
|
||||||
|
surf.getRow = surfGetRow;
|
||||||
|
|
||||||
scr->fbAll = fb;
|
scr->fbAll = fb;
|
||||||
scr->charsAll = chars;
|
scr->charsAll = chars;
|
||||||
|
|
@ -1610,6 +1618,16 @@ static uint32_t surfGet(const void *ctx, int32_t x, int32_t y) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void surfGetRow(const void *ctx, int32_t x0, int32_t x1, int32_t y, uint32_t *out) {
|
||||||
|
const BasScreenT *scr = (const BasScreenT *)ctx;
|
||||||
|
const uint8_t *row = scr->fb + y * scr->width;
|
||||||
|
|
||||||
|
for (int32_t x = x0; x <= x1; x++) {
|
||||||
|
out[x - x0] = row[x];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static void surfSpan(void *ctx, int32_t x0, int32_t x1, int32_t y, uint32_t pixel) {
|
static void surfSpan(void *ctx, int32_t x0, int32_t x1, int32_t y, uint32_t pixel) {
|
||||||
BasScreenT *scr = (BasScreenT *)ctx;
|
BasScreenT *scr = (BasScreenT *)ctx;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -456,6 +456,11 @@ void basObjectClear(BasObjectT *obj) {
|
||||||
obj->itemCount = 0;
|
obj->itemCount = 0;
|
||||||
obj->itemCap = 0;
|
obj->itemCap = 0;
|
||||||
|
|
||||||
|
free(obj->keyIndex);
|
||||||
|
obj->keyIndex = NULL;
|
||||||
|
obj->keyIndexCap = 0;
|
||||||
|
obj->keyIndexUsed = 0;
|
||||||
|
|
||||||
for (int32_t i = 0; i < fieldCount; i++) {
|
for (int32_t i = 0; i < fieldCount; i++) {
|
||||||
basValRelease(&fields[i]);
|
basValRelease(&fields[i]);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,7 @@
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
|
|
||||||
#include "../compiler/opcodes.h"
|
#include "../compiler/opcodes.h"
|
||||||
|
#include "dvxHash.h"
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// Reference-counted string
|
// Reference-counted string
|
||||||
|
|
@ -318,11 +319,6 @@ typedef struct {
|
||||||
BasObjectT *next;
|
BasObjectT *next;
|
||||||
} BasObjLinkT;
|
} BasObjLinkT;
|
||||||
|
|
||||||
// FNV-1a parameters shared by the constant-pool interning hash, the
|
|
||||||
// case-insensitive symbol hash (compiler/scope.c) and Collection keys.
|
|
||||||
#define BAS_FNV1A_OFFSET 0x811C9DC5u
|
|
||||||
#define BAS_FNV1A_PRIME 0x01000193u
|
|
||||||
|
|
||||||
// One Collection item: an optional key (NULL = none), its hash (a lookup
|
// One Collection item: an optional key (NULL = none), its hash (a lookup
|
||||||
// compares the text only when the hashes match) and the value.
|
// compares the text only when the hashes match) and the value.
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
|
@ -339,6 +335,9 @@ struct BasObjectS {
|
||||||
int32_t itemCap;
|
int32_t itemCap;
|
||||||
BasValueT *fields; // class instance: member variables
|
BasValueT *fields; // class instance: member variables
|
||||||
BasCollItemT *items; // Collection: items in order
|
BasCollItemT *items; // Collection: items in order
|
||||||
|
int32_t *keyIndex; // Collection: item position + 1 by key hash, open addressing (NULL = not built)
|
||||||
|
int32_t keyIndexCap; // its slots (a power of two)
|
||||||
|
int32_t keyIndexUsed; // keyed items in it
|
||||||
BasObjFinalizeFnT finalize; // Class_Terminate hook (NULL = free at once)
|
BasObjFinalizeFnT finalize; // Class_Terminate hook (NULL = free at once)
|
||||||
void *finalizeCtx;
|
void *finalizeCtx;
|
||||||
BasObjLinkT links[BAS_OBJ_LIST_COUNT]; // places on the owner's lists (BasObjListE)
|
BasObjLinkT links[BAS_OBJ_LIST_COUNT]; // places on the owner's lists (BasObjListE)
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -41,6 +41,7 @@
|
||||||
#include "values.h"
|
#include "values.h"
|
||||||
#include "../compiler/basEvents.h"
|
#include "../compiler/basEvents.h"
|
||||||
#include "../compiler/opcodes.h"
|
#include "../compiler/opcodes.h"
|
||||||
|
#include "../../../../libs/kpunch/sql/dvxSql.h"
|
||||||
#include "dvxTypes.h"
|
#include "dvxTypes.h"
|
||||||
#include "dvxPlat.h"
|
#include "dvxPlat.h"
|
||||||
|
|
||||||
|
|
@ -228,6 +229,12 @@ typedef const char *(*BasUiDefaultPropFnT)(void *ctx, void *objRef);
|
||||||
// cannot be read.
|
// cannot be read.
|
||||||
typedef void *(*BasUiLoadPictureFnT)(void *ctx, const char *path, int32_t *outErr);
|
typedef void *(*BasUiLoadPictureFnT)(void *ctx, const char *path, int32_t *outErr);
|
||||||
|
|
||||||
|
// LoadPictureData(bytes): a Picture object for the len bytes of a picture
|
||||||
|
// file ("" = an empty picture; the same bytes give the same object). NULL
|
||||||
|
// with *outErr set to a BASIC error (invalid picture, out of memory) when
|
||||||
|
// they are not a picture DVX can show.
|
||||||
|
typedef void *(*BasUiLoadPictureDataFnT)(void *ctx, const char *data, int32_t len, int32_t *outErr);
|
||||||
|
|
||||||
// LoadFormText(text): a form loaded from .frm text (its own copy when a
|
// LoadFormText(text): a form loaded from .frm text (its own copy when a
|
||||||
// form of its name is loaded), NULL when the text does not load.
|
// form of its name is loaded), NULL when the text does not load.
|
||||||
typedef void *(*BasUiLoadFormTextFnT)(void *ctx, const char *text);
|
typedef void *(*BasUiLoadFormTextFnT)(void *ctx, const char *text);
|
||||||
|
|
@ -255,29 +262,32 @@ typedef struct {
|
||||||
BasUiLoadPictureFnT loadPicture;
|
BasUiLoadPictureFnT loadPicture;
|
||||||
BasUiDefaultPropFnT defaultProp;
|
BasUiDefaultPropFnT defaultProp;
|
||||||
BasUiLoadFormTextFnT loadFormText;
|
BasUiLoadFormTextFnT loadFormText;
|
||||||
|
BasUiLoadPictureDataFnT loadPictureData;
|
||||||
void *ctx; // passed as first arg to all callbacks
|
void *ctx; // passed as first arg to all callbacks
|
||||||
} BasUiCallbacksT;
|
} BasUiCallbacksT;
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// SQL callbacks (host-provided)
|
// SQL callbacks (host-provided: the dvxsql library's functions, by type)
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
int32_t (*sqlOpen)(const char *path);
|
__typeof__(dvxSqlOpen) *sqlOpen;
|
||||||
void (*sqlClose)(int32_t db);
|
__typeof__(dvxSqlClose) *sqlClose;
|
||||||
bool (*sqlExec)(int32_t db, const char *sql);
|
__typeof__(dvxSqlExec) *sqlExec;
|
||||||
const char *(*sqlError)(int32_t db);
|
__typeof__(dvxSqlError) *sqlError;
|
||||||
int32_t (*sqlQuery)(int32_t db, const char *sql);
|
__typeof__(dvxSqlQuery) *sqlQuery;
|
||||||
bool (*sqlNext)(int32_t rs);
|
__typeof__(dvxSqlNext) *sqlNext;
|
||||||
bool (*sqlEof)(int32_t rs);
|
__typeof__(dvxSqlEof) *sqlEof;
|
||||||
int32_t (*sqlFieldCount)(int32_t rs);
|
__typeof__(dvxSqlFieldCount) *sqlFieldCount;
|
||||||
const char *(*sqlFieldName)(int32_t rs, int32_t col);
|
__typeof__(dvxSqlFieldName) *sqlFieldName;
|
||||||
const char *(*sqlFieldText)(int32_t rs, int32_t col);
|
__typeof__(dvxSqlFieldText) *sqlFieldText;
|
||||||
const char *(*sqlFieldByName)(int32_t rs, const char *name);
|
__typeof__(dvxSqlFieldByName) *sqlFieldByName;
|
||||||
int32_t (*sqlFieldInt)(int32_t rs, int32_t col);
|
__typeof__(dvxSqlFieldInt) *sqlFieldInt;
|
||||||
double (*sqlFieldDbl)(int32_t rs, int32_t col);
|
__typeof__(dvxSqlFieldDbl) *sqlFieldDbl;
|
||||||
void (*sqlFreeResult)(int32_t rs);
|
__typeof__(dvxSqlFreeResult) *sqlFreeResult;
|
||||||
int32_t (*sqlAffectedRows)(int32_t db);
|
__typeof__(dvxSqlAffectedRows) *sqlAffectedRows;
|
||||||
|
__typeof__(dvxSqlSetPump) *sqlSetPump;
|
||||||
|
__typeof__(dvxSqlEscape) *sqlEscape;
|
||||||
} BasSqlCallbacksT;
|
} BasSqlCallbacksT;
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
@ -313,9 +323,13 @@ typedef struct {
|
||||||
// soundBusy says the statement must still wait (foreground music); after
|
// soundBusy says the statement must still wait (foreground music); after
|
||||||
// frameStart(hz) (the host's frame clock moves on to its next frame at hz
|
// frameStart(hz) (the host's frame clock moves on to its next frame at hz
|
||||||
// frames a second) likewise while frameBusy says that frame has not
|
// frames a second) likewise while frameBusy says that frame has not
|
||||||
// begun. Without beep / sound / play the statements do nothing and
|
// begun. SHELL hands its command line to shellStart (the host runs it
|
||||||
// PLAY(n) is 0, without stick / strig they read 0, and FRAMEWAIT without
|
// with DVX stepped aside: false when it cannot), keeps the host's events
|
||||||
// frameStart raises "Device unavailable".
|
// running while shellBusy, and returns shellResult (the exit code, -1 when
|
||||||
|
// it did not run). Without beep / sound / play the statements do nothing
|
||||||
|
// and PLAY(n) is 0, without stick / strig they read 0, FRAMEWAIT without
|
||||||
|
// frameStart raises "Device unavailable", and SHELL without shellStart
|
||||||
|
// runs the command with system() (BASIC outside DVX: the runner, BASCOMP).
|
||||||
|
|
||||||
// QBasic's ranges: SOUND frequency (Hz) and duration (clock ticks, 18.2
|
// QBasic's ranges: SOUND frequency (Hz) and duration (clock ticks, 18.2
|
||||||
// a second), STICK(0..3), STRIG(0..7)
|
// a second), STICK(0..3), STRIG(0..7)
|
||||||
|
|
@ -335,6 +349,9 @@ typedef struct {
|
||||||
int32_t (*strig)(void *ctx, int32_t n);
|
int32_t (*strig)(void *ctx, int32_t n);
|
||||||
void (*frameStart)(void *ctx, int32_t hz);
|
void (*frameStart)(void *ctx, int32_t hz);
|
||||||
bool (*frameBusy)(void *ctx);
|
bool (*frameBusy)(void *ctx);
|
||||||
|
bool (*shellStart)(void *ctx, const char *command);
|
||||||
|
bool (*shellBusy)(void *ctx);
|
||||||
|
int32_t (*shellResult)(void *ctx);
|
||||||
void *ctx;
|
void *ctx;
|
||||||
} BasDeviceCallbacksT;
|
} BasDeviceCallbacksT;
|
||||||
|
|
||||||
|
|
@ -837,6 +854,17 @@ typedef struct {
|
||||||
void *inputCtx;
|
void *inputCtx;
|
||||||
BasDoEventsFnT doEventsFn;
|
BasDoEventsFnT doEventsFn;
|
||||||
void *doEventsCtx;
|
void *doEventsCtx;
|
||||||
|
|
||||||
|
// The slice clock (basVmSetSliceClock): a counter the host's timer
|
||||||
|
// interrupt advances, read after each instruction run out of line
|
||||||
|
// (calls, properties, built-ins); NULL: slices count steps only.
|
||||||
|
const volatile uint32_t *sliceClock;
|
||||||
|
uint32_t sliceSpan; // its counts a slice may last
|
||||||
|
uint32_t sliceStart; // its value when the slice began
|
||||||
|
uint64_t (*microsFn)(void); // basVmSetMicrosClock: TIMER's fine clock (NULL: the wall clock alone)
|
||||||
|
int64_t timerWall; // TIMER: the wall clock's last reading, in microseconds (-1: none yet)
|
||||||
|
uint64_t timerSeen; // microsFn when it came
|
||||||
|
int32_t runLimit; // slices one run or call may last, waits refused (basVmSetRunLimit; 0: no limit)
|
||||||
BasBreakpointFnT breakpointFn;
|
BasBreakpointFnT breakpointFn;
|
||||||
void *breakpointCtx;
|
void *breakpointCtx;
|
||||||
BasEventHostFnT eventHostFn; // UserControl events to the host (NULL: none)
|
BasEventHostFnT eventHostFn; // UserControl events to the host (NULL: none)
|
||||||
|
|
@ -907,6 +935,12 @@ typedef struct {
|
||||||
void *dirHandle; // DIR * (opaque here; cast in vm.c)
|
void *dirHandle; // DIR * (opaque here; cast in vm.c)
|
||||||
char dirPattern[DVX_MAX_PATH]; // filename glob for Dir$
|
char dirPattern[DVX_MAX_PATH]; // filename glob for Dir$
|
||||||
char dirPath[DVX_MAX_PATH]; // directory being enumerated
|
char dirPath[DVX_MAX_PATH]; // directory being enumerated
|
||||||
|
|
||||||
|
// IniRead / IniWrite's last file, kept parsed (iniPrefs in vm.c)
|
||||||
|
void *iniPrefs; // PrefsHandleT * (NULL = none kept)
|
||||||
|
char iniPath[DVX_MAX_PATH];
|
||||||
|
int64_t iniSize; // its size and time when kept (-1: there was no file)
|
||||||
|
int64_t iniTime;
|
||||||
} BasVmT;
|
} BasVmT;
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
@ -955,6 +989,30 @@ void basVmSetDoEventsCallback(BasVmT *vm, BasDoEventsFnT fn, void *ctx);
|
||||||
void basVmSetInputCallback(BasVmT *vm, BasInputFnT fn, void *ctx);
|
void basVmSetInputCallback(BasVmT *vm, BasInputFnT fn, void *ctx);
|
||||||
void basVmSetPrintCallback(BasVmT *vm, BasPrintFnT fn, void *ctx);
|
void basVmSetPrintCallback(BasVmT *vm, BasPrintFnT fn, void *ctx);
|
||||||
|
|
||||||
|
// For a VM with no event pump that must never keep the CPU (an installed
|
||||||
|
// control file's, shared by every program): a run, or a call into it (an
|
||||||
|
// event), that lasts more than slices slices is stopped with error 18,
|
||||||
|
// which ON ERROR cannot trap; SLEEP, FRAMEWAIT, and SHELL raise error 5
|
||||||
|
// instead of blocking, and BEEP, SOUND, and PLAY do not wait for
|
||||||
|
// foreground music. 0: no limit (the default).
|
||||||
|
void basVmSetRunLimit(BasVmT *vm, int32_t slices);
|
||||||
|
|
||||||
|
// Give the VM a clock for its slices: clock is a counter the host's timer
|
||||||
|
// interrupt advances (reading it costs one load), span how far it may move
|
||||||
|
// in one slice. A slice (a basVmRun with a step limit, or the stretch of
|
||||||
|
// an event handler between two doEvents) then also ends once the clock has
|
||||||
|
// moved span, at the first instruction run out of line after that, so a
|
||||||
|
// loop of long calls (a file copy, a SQL statement) gives the host its turn
|
||||||
|
// as often as a loop of quick instructions does. NULL: steps only.
|
||||||
|
void basVmSetSliceClock(BasVmT *vm, const volatile uint32_t *clock, uint32_t span);
|
||||||
|
|
||||||
|
// A fine monotonic clock in microseconds (platformMicros) for TIMER: the
|
||||||
|
// wall clock moves in coarse steps on DOS (a BIOS tick, about 55 ms), and
|
||||||
|
// the time since it last moved is added from this, never a whole step, so
|
||||||
|
// TIMER rises smoothly and never passes the wall clock's next reading.
|
||||||
|
// NULL: the wall clock alone.
|
||||||
|
void basVmSetMicrosClock(BasVmT *vm, uint64_t (*micros)(void));
|
||||||
|
|
||||||
// Set the printer callbacks (LPRINT, OPEN "LPTn:").
|
// Set the printer callbacks (LPRINT, OPEN "LPTn:").
|
||||||
void basVmSetPrinterCallbacks(BasVmT *vm, const BasPrinterCallbacksT *printer);
|
void basVmSetPrinterCallbacks(BasVmT *vm, const BasPrinterCallbacksT *printer);
|
||||||
|
|
||||||
|
|
@ -965,8 +1023,10 @@ void basVmSetDeviceCallbacks(BasVmT *vm, const BasDeviceCallbacksT *device);
|
||||||
// Set the program screen callbacks.
|
// Set the program screen callbacks.
|
||||||
void basVmSetScreenHost(BasVmT *vm, const BasScreenHostT *host);
|
void basVmSetScreenHost(BasVmT *vm, const BasScreenHostT *host);
|
||||||
|
|
||||||
// Set UI callbacks (for form/control system).
|
// Set the SQL callbacks (the dvxsql library's functions).
|
||||||
void basVmSetSqlCallbacks(BasVmT *vm, const BasSqlCallbacksT *sql);
|
void basVmSetSqlCallbacks(BasVmT *vm, const BasSqlCallbacksT *sql);
|
||||||
|
|
||||||
|
// Set UI callbacks (for form/control system).
|
||||||
void basVmSetUiCallbacks(BasVmT *vm, const BasUiCallbacksT *ui);
|
void basVmSetUiCallbacks(BasVmT *vm, const BasUiCallbacksT *ui);
|
||||||
|
|
||||||
// Set external library callbacks (for DECLARE LIBRARY support).
|
// Set external library callbacks (for DECLARE LIBRARY support).
|
||||||
|
|
|
||||||
|
|
@ -181,7 +181,7 @@ int main(int argc, char **argv) {
|
||||||
printf("Compiling %d file(s)...\n", (int)fileCount);
|
printf("Compiling %d file(s)...\n", (int)fileCount);
|
||||||
|
|
||||||
BasCompileErrorT err;
|
BasCompileErrorT err;
|
||||||
BasModuleT *mod = basBuildCompile(project.source, project.sourceLen, project.optionExplicit, NULL, NULL, &err);
|
BasModuleT *mod = basBuildCompile(project.source, project.sourceLen, project.optionExplicit, NULL, NULL, &err, NULL, NULL);
|
||||||
|
|
||||||
if (!mod) {
|
if (!mod) {
|
||||||
const char *errPath = NULL;
|
const char *errPath = NULL;
|
||||||
|
|
|
||||||
|
|
@ -51,13 +51,11 @@
|
||||||
// App descriptor
|
// App descriptor
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
#define STUB_STACK_SIZE 65536
|
|
||||||
|
|
||||||
SHELL_APP_DESCRIPTOR(
|
SHELL_APP_DESCRIPTOR(
|
||||||
.name = BAS_DEFAULT_APP_NAME,
|
.name = BAS_DEFAULT_APP_NAME,
|
||||||
.hasMainLoop = true,
|
.hasMainLoop = true,
|
||||||
.multiInstance = false,
|
.multiInstance = false,
|
||||||
.stackSize = STUB_STACK_SIZE,
|
.stackSize = BAS_RUN_STACK_SIZE,
|
||||||
.priority = TS_PRIORITY_NORMAL
|
.priority = TS_PRIORITY_NORMAL
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -71,7 +69,6 @@ static BasFormRtT *sRt = NULL; // live while appMain runs the program (F1 que
|
||||||
|
|
||||||
|
|
||||||
// Function prototypes (alphabetical; appMain last)
|
// Function prototypes (alphabetical; appMain last)
|
||||||
void appShutdown(void);
|
|
||||||
static bool stubDoEvents(void *ctx);
|
static bool stubDoEvents(void *ctx);
|
||||||
static void stubHelpQuery(void *ctx);
|
static void stubHelpQuery(void *ctx);
|
||||||
static bool stubInput(void *ctx, const char *prompt, char *buf, int32_t bufSize);
|
static bool stubInput(void *ctx, const char *prompt, char *buf, int32_t bufSize);
|
||||||
|
|
@ -80,16 +77,6 @@ static void stubPrint(void *ctx, const char *text, bool newline);
|
||||||
int32_t appMain(DxeAppContextT *ctx);
|
int32_t appMain(DxeAppContextT *ctx);
|
||||||
|
|
||||||
|
|
||||||
// Resolved by the shell as _appShutdown and called when the app is reaped
|
|
||||||
// after appMain returned (which already ran basFormRtDestroy, so this
|
|
||||||
// finds the serial ports closed). A force kill runs none of the app's
|
|
||||||
// code: basrt's kill hook (basRunHostAppKilled) ends the runtime then,
|
|
||||||
// serial ports and idle pollers included, while its windows are there.
|
|
||||||
void appShutdown(void) {
|
|
||||||
basFormRtSerialShutdown();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static bool stubDoEvents(void *ctx) {
|
static bool stubDoEvents(void *ctx) {
|
||||||
(void)ctx;
|
(void)ctx;
|
||||||
|
|
||||||
|
|
@ -97,7 +84,7 @@ static bool stubDoEvents(void *ctx) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return dvxUpdate(sAc);
|
return sRt ? basFormRtUpdate(sRt) : dvxUpdate(sAc);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7100,8 +7100,9 @@ static void testScreenModesPages(void) {
|
||||||
|
|
||||||
|
|
||||||
// Phase 1 library functions: the string functions (REPLACE$, SPLIT, JOIN$,
|
// Phase 1 library functions: the string functions (REPLACE$, SPLIT, JOIN$,
|
||||||
// INSTRREV, STRCOMP, STRREVERSE$, FILTER, LSET / RSET, IIF, CHOOSE) and
|
// INSTRREV, STRCOMP, STRREVERSE$, CHRW$, ASCW, FILTER, LSET / RSET, IIF,
|
||||||
// the DATE type with its functions, literals, arithmetic and Format$.
|
// CHOOSE) and the DATE type with its functions, literals, arithmetic and
|
||||||
|
// Format$.
|
||||||
static void testStringsAndDates(void) {
|
static void testStringsAndDates(void) {
|
||||||
runProgram("Replace$",
|
runProgram("Replace$",
|
||||||
"PRINT Replace$(\"aXbXc\", \"X\", \"-\")\n"
|
"PRINT Replace$(\"aXbXc\", \"X\", \"-\")\n"
|
||||||
|
|
@ -7164,6 +7165,25 @@ static void testStringsAndDates(void) {
|
||||||
"apple-banana-cherry\napple banana cherry\nxyz\n1,22,333\n[]\n0 banana\napple+cherry\nbanana\n-1 \n"
|
"apple-banana-cherry\napple banana cherry\nxyz\n1,22,333\n[]\n0 banana\napple+cherry\nbanana\n-1 \n"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
runProgram("Dir$ takes DOS's wildcards: *.* is every file, [ is a plain character",
|
||||||
|
"MKDIR \"dirwild\"\n"
|
||||||
|
"OPEN \"dirwild/plain\" FOR OUTPUT AS #1: CLOSE #1\n"
|
||||||
|
"OPEN \"dirwild/a[1].txt\" FOR OUTPUT AS #1: CLOSE #1\n"
|
||||||
|
"OPEN \"dirwild/b.TXT\" FOR OUTPUT AS #1: CLOSE #1\n"
|
||||||
|
"n = 0\n"
|
||||||
|
"f$ = Dir$(\"dirwild/*.*\")\n"
|
||||||
|
"DO WHILE f$ <> \"\"\n"
|
||||||
|
" n = n + 1\n"
|
||||||
|
" f$ = Dir$()\n"
|
||||||
|
"LOOP\n"
|
||||||
|
"PRINT n; Dir$(\"dirwild/a[1]*\"); \" \"; Dir$(\"dirwild/B.t?t\")\n"
|
||||||
|
"KILL \"dirwild/plain\"\n"
|
||||||
|
"KILL \"dirwild/a[1].txt\"\n"
|
||||||
|
"KILL \"dirwild/b.TXT\"\n"
|
||||||
|
"RMDIR \"dirwild\"\n",
|
||||||
|
"3 a[1].txt b.TXT\n"
|
||||||
|
);
|
||||||
|
|
||||||
runProgram("InStrRev, StrComp and StrReverse$",
|
runProgram("InStrRev, StrComp and StrReverse$",
|
||||||
"PRINT InStrRev(\"abcabc\", \"bc\")\n"
|
"PRINT InStrRev(\"abcabc\", \"bc\")\n"
|
||||||
"PRINT InStrRev(\"abcabc\", \"bc\", 4)\n"
|
"PRINT InStrRev(\"abcabc\", \"bc\", 4)\n"
|
||||||
|
|
@ -7178,6 +7198,26 @@ static void testStringsAndDates(void) {
|
||||||
"5 \n2 \n0 \n3 \n2 \n0 \n0 \n-1 1 0 \n-1 0 -1 \ncba[]\n"
|
"5 \n2 \n0 \n3 \n2 \n0 \n0 \n-1 1 0 \n-1 0 -1 \ncba[]\n"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
runProgram("FULLSCREEN reads the program screen's state without making one",
|
||||||
|
"PRINT FULLSCREEN; NOT FULLSCREEN\n",
|
||||||
|
"False True \n"
|
||||||
|
);
|
||||||
|
|
||||||
|
runProgram("ChrW$ and AscW: Unicode code points as DVX text",
|
||||||
|
"c = 130\n"
|
||||||
|
"PRINT AscW(\"A\"); AscW(Chr$(10)); AscW(Chr$(c)); AscW(Chr$(196))\n"
|
||||||
|
"PRINT Asc(ChrW$(&HE9)); Asc(ChrW(&H2500)); Asc(ChrW$(65)); Asc(ChrW$(&H263A))\n"
|
||||||
|
"PRINT ChrW$(&H4E2D); LEN(ChrW$(&H1F600))\n"
|
||||||
|
"e$ = \"\"\n"
|
||||||
|
"ON ERROR GOTO h\n"
|
||||||
|
"PRINT AscW(e$)\n"
|
||||||
|
"PRINT ChrW$(-1)\n"
|
||||||
|
"PRINT ChrW$(&H110000)\n"
|
||||||
|
"END\n"
|
||||||
|
"h: PRINT \"err\"; ERR: RESUME NEXT\n",
|
||||||
|
"65 10 233 9472 \n130 196 65 1 \n?1 \nerr5 \nerr5 \nerr5 \n"
|
||||||
|
);
|
||||||
|
|
||||||
runProgram("Drag and clipboard constants; Control, Form and Object types",
|
runProgram("Drag and clipboard constants; Control, Form and Object types",
|
||||||
"PRINT vbManual; vbAutomatic; vbBeginDrag; vbEndDrag\n"
|
"PRINT vbManual; vbAutomatic; vbBeginDrag; vbEndDrag\n"
|
||||||
"PRINT vbEnter; vbLeave; vbOver; vbCFText\n"
|
"PRINT vbEnter; vbLeave; vbOver; vbCFText\n"
|
||||||
|
|
@ -8716,7 +8756,9 @@ int main(void) {
|
||||||
}
|
}
|
||||||
|
|
||||||
printf(found ? "PASS: OP_CALL_EXTERN emitted\n" : "FAIL: OP_CALL_EXTERN not emitted\n");
|
printf(found ? "PASS: OP_CALL_EXTERN emitted\n" : "FAIL: OP_CALL_EXTERN not emitted\n");
|
||||||
if (!found) { gFailCount++; }
|
if (!found) {
|
||||||
|
gFailCount++;
|
||||||
|
}
|
||||||
basModuleFree(mod);
|
basModuleFree(mod);
|
||||||
} else {
|
} else {
|
||||||
printf("MODULE BUILD FAILED\n");
|
printf("MODULE BUILD FAILED\n");
|
||||||
|
|
@ -8792,7 +8834,9 @@ int main(void) {
|
||||||
printf("Calling Command1_Click via basVmCallSub: ");
|
printf("Calling Command1_Click via basVmCallSub: ");
|
||||||
bool callOk = basVmCallSub(vm, p->codeAddr);
|
bool callOk = basVmCallSub(vm, p->codeAddr);
|
||||||
printf("result=%s\n", callOk ? "ok" : "FAIL");
|
printf("result=%s\n", callOk ? "ok" : "FAIL");
|
||||||
if (!callOk) { gFailCount++; }
|
if (!callOk) {
|
||||||
|
gFailCount++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
basVmDestroy(vm);
|
basVmDestroy(vm);
|
||||||
|
|
@ -9835,10 +9879,14 @@ int main(void) {
|
||||||
const BasProcEntryT *p1 = basModuleFindProc(mod, "Form1_Load");
|
const BasProcEntryT *p1 = basModuleFindProc(mod, "Form1_Load");
|
||||||
const BasProcEntryT *p2 = basModuleFindProc(mod, "Command1_Click");
|
const BasProcEntryT *p2 = basModuleFindProc(mod, "Command1_Click");
|
||||||
|
|
||||||
if (p1) { basVmCallSub(vm, p1->codeAddr); }
|
if (p1) {
|
||||||
|
basVmCallSub(vm, p1->codeAddr);
|
||||||
|
}
|
||||||
else { printf("Form1_Load NOT FOUND\n"); }
|
else { printf("Form1_Load NOT FOUND\n"); }
|
||||||
|
|
||||||
if (p2) { basVmCallSub(vm, p2->codeAddr); }
|
if (p2) {
|
||||||
|
basVmCallSub(vm, p2->codeAddr);
|
||||||
|
}
|
||||||
else { printf("Command1_Click NOT FOUND\n"); }
|
else { printf("Command1_Click NOT FOUND\n"); }
|
||||||
|
|
||||||
basVmDestroy(vm);
|
basVmDestroy(vm);
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,8 @@
|
||||||
#include "dvxRes.h"
|
#include "dvxRes.h"
|
||||||
|
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
|
#include <time.h>
|
||||||
|
#include <utime.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include "compiler/strip.h"
|
#include "compiler/strip.h"
|
||||||
#include "runtime/serialize.h"
|
#include "runtime/serialize.h"
|
||||||
|
|
@ -73,6 +75,8 @@
|
||||||
|
|
||||||
// Failure detail text of a check.
|
// Failure detail text of a check.
|
||||||
#define TEST_DETAIL_LEN 512
|
#define TEST_DETAIL_LEN 512
|
||||||
|
#define SLICE_SPAN 2 // testSliceClock: clock counts a slice may last
|
||||||
|
#define SLICE_STEPS 1000000 // testSliceClock: far more steps than the slice runs
|
||||||
#define TEST_IMPL_CORRUPTIONS 3 // testInterfaceMaps: bad entry, short map, bad interface
|
#define TEST_IMPL_CORRUPTIONS 3 // testInterfaceMaps: bad entry, short map, bad interface
|
||||||
#define TEST_ENUM_CORRUPTIONS 7 // testUserControlEnums: index past the table, below none, on a method, in another class, an Enum without members, an empty name, two names alike
|
#define TEST_ENUM_CORRUPTIONS 7 // testUserControlEnums: index past the table, below none, on a method, in another class, an Enum without members, an empty name, two names alike
|
||||||
#define TEST_ENUM_WIRE_NAME "\x06\x00" "Circle" // the member Circle on the wire: uint16 length, then the name
|
#define TEST_ENUM_WIRE_NAME "\x06\x00" "Circle" // the member Circle on the wire: uint16 length, then the name
|
||||||
|
|
@ -80,6 +84,13 @@
|
||||||
#define TEST_ENUM_WIRE_NUL 5 // a byte of the name, made NUL
|
#define TEST_ENUM_WIRE_NUL 5 // a byte of the name, made NUL
|
||||||
#define TS_BIG_FILE "ts_big.tmp" // a sparse file past 2 GB (testBattery)
|
#define TS_BIG_FILE "ts_big.tmp" // a sparse file past 2 GB (testBattery)
|
||||||
#define TS_BIG_SIZE 3000000000LL // bytes: a FAT32 file may reach 4 GB - 1
|
#define TS_BIG_SIZE 3000000000LL // bytes: a FAT32 file may reach 4 GB - 1
|
||||||
|
#define TS_COMPILE_UNITS 4 // testCompileUnits: two assignments, a SUB, a PRINT
|
||||||
|
#define TS_DIR_PUMP_DIR "ts_dirp" // testDirPumps' folder
|
||||||
|
#define TS_DIR_PUMP_FILES 200 // files in it Dir$ passes over
|
||||||
|
#define TS_DIR_PUMPS_MIN 2 // pumps at least: 200 passed over in two calls, a pump per 64
|
||||||
|
#define TS_PUMPS_MANY 1000 // a countdown no test runs out
|
||||||
|
#define TS_INI_FILE "ts_inic.tmp" // testIniCache's file
|
||||||
|
#define TS_INI_SETTLED_SEC 3600 // its time: an hour old, past the 2 s a FAT time cannot tell apart
|
||||||
|
|
||||||
static CaptureT *sUiCap = NULL;
|
static CaptureT *sUiCap = NULL;
|
||||||
static char sUiRefs[UI_STUB_REF_COUNT][UI_STUB_REF_LEN];
|
static char sUiRefs[UI_STUB_REF_COUNT][UI_STUB_REF_LEN];
|
||||||
|
|
@ -238,6 +249,7 @@ static bool deviceSoundBusyStub(void *ctx);
|
||||||
static void deviceSoundStub(void *ctx, double hz, double ticks);
|
static void deviceSoundStub(void *ctx, double hz, double ticks);
|
||||||
static int32_t deviceStickStub(void *ctx, int32_t n);
|
static int32_t deviceStickStub(void *ctx, int32_t n);
|
||||||
static int32_t deviceStrigStub(void *ctx, int32_t n);
|
static int32_t deviceStrigStub(void *ctx, int32_t n);
|
||||||
|
static void dirPumpFiles(bool make);
|
||||||
static bool doEventsAbortStub(void *ctx);
|
static bool doEventsAbortStub(void *ctx);
|
||||||
static bool doEventsCountdownStub(void *ctx);
|
static bool doEventsCountdownStub(void *ctx);
|
||||||
static bool doEventsResumeStub(void *ctx);
|
static bool doEventsResumeStub(void *ctx);
|
||||||
|
|
@ -248,6 +260,7 @@ static void handEmit16(HandCodeT *h, int16_t v);
|
||||||
static void handEmit32(HandCodeT *h, int32_t v);
|
static void handEmit32(HandCodeT *h, int32_t v);
|
||||||
static void handEmit8(HandCodeT *h, uint8_t b);
|
static void handEmit8(HandCodeT *h, uint8_t b);
|
||||||
static void handEmitU16(HandCodeT *h, uint16_t v);
|
static void handEmitU16(HandCodeT *h, uint16_t v);
|
||||||
|
static bool iniRewriteStub(void *ctx);
|
||||||
static bool inputStub(void *ctx, const char *prompt, char *buf, int32_t bufSize);
|
static bool inputStub(void *ctx, const char *prompt, char *buf, int32_t bufSize);
|
||||||
static int32_t memberIndex(const BasClassInfoT *cls, const char *memberName, uint8_t kind);
|
static int32_t memberIndex(const BasClassInfoT *cls, const char *memberName, uint8_t kind);
|
||||||
static MiniCtrlT *miniAddCtrl(MiniFormT *f, const char *name, const char *typeName);
|
static MiniCtrlT *miniAddCtrl(MiniFormT *f, const char *name, const char *typeName);
|
||||||
|
|
@ -256,6 +269,7 @@ static void miniFireCtrlEvent(MiniRtT *rt, MiniCtrlT *ctrl, const char *eventNam
|
||||||
static MiniFormT *miniResolveOwningForm(MiniRtT *rt, const BasProcEntryT *proc);
|
static MiniFormT *miniResolveOwningForm(MiniRtT *rt, const BasProcEntryT *proc);
|
||||||
static void miniSetEvent(MiniCtrlT *ctrl, const char *eventName, const char *handlerName);
|
static void miniSetEvent(MiniCtrlT *ctrl, const char *eventName, const char *handlerName);
|
||||||
static bool moduleHasOpcode(const BasModuleT *mod, uint8_t op);
|
static bool moduleHasOpcode(const BasModuleT *mod, uint8_t op);
|
||||||
|
static void printTickStub(void *ctx, const char *text, bool newline);
|
||||||
static void reportFail(const char *name, const char *detail);
|
static void reportFail(const char *name, const char *detail);
|
||||||
static void reportFailDiff(const char *name, const char *expected, const char *got);
|
static void reportFailDiff(const char *name, const char *expected, const char *got);
|
||||||
static void reportPass(const char *name);
|
static void reportPass(const char *name);
|
||||||
|
|
@ -267,9 +281,11 @@ static void testBytecode(const char *name, const char *source, const uint8_t *ex
|
||||||
static void testClassRoundTrip(const char *name);
|
static void testClassRoundTrip(const char *name);
|
||||||
static void testCommandArgs(const char *name);
|
static void testCommandArgs(const char *name);
|
||||||
static void testCompileError(const char *name, const char *source, const char *needle);
|
static void testCompileError(const char *name, const char *source, const char *needle);
|
||||||
|
static void testCompileUnits(void);
|
||||||
static void testDebuggerApi(void);
|
static void testDebuggerApi(void);
|
||||||
static void testDebugInfoRoundTrip(const char *name);
|
static void testDebugInfoRoundTrip(const char *name);
|
||||||
static void testDeviceStatements(void);
|
static void testDeviceStatements(void);
|
||||||
|
static void testDirPumps(void);
|
||||||
static void testDispatchEq(const char *name, const TestDispatchT *d, const char *expected);
|
static void testDispatchEq(const char *name, const TestDispatchT *d, const char *expected);
|
||||||
static void testEarlyBoundOpcodes(void);
|
static void testEarlyBoundOpcodes(void);
|
||||||
static void testEq(const char *name, const char *source, const char *expected);
|
static void testEq(const char *name, const char *source, const char *expected);
|
||||||
|
|
@ -278,6 +294,7 @@ static void testHandAssembled(void);
|
||||||
static void testHandBattery(void);
|
static void testHandBattery(void);
|
||||||
static void testHandRun(const char *name, const HandCodeT *h, BasStringT **consts, int32_t constCount, int32_t localCount, const char *expected);
|
static void testHandRun(const char *name, const HandCodeT *h, BasStringT **consts, int32_t constCount, int32_t localCount, const char *expected);
|
||||||
static void testHandRunError(const char *name, const HandCodeT *h, BasStringT **consts, int32_t constCount, int32_t localCount, const char *needle);
|
static void testHandRunError(const char *name, const HandCodeT *h, BasStringT **consts, int32_t constCount, int32_t localCount, const char *needle);
|
||||||
|
static void testIniCache(void);
|
||||||
static void testInputCallback(const char *name);
|
static void testInputCallback(const char *name);
|
||||||
static void testInterfaceMaps(const char *name);
|
static void testInterfaceMaps(const char *name);
|
||||||
static void testLexerTokens(const char *name, const char *source, const ExpectedTokenT *expected, int32_t expectedCount);
|
static void testLexerTokens(const char *name, const char *source, const ExpectedTokenT *expected, int32_t expectedCount);
|
||||||
|
|
@ -290,6 +307,7 @@ static void testRuntimeError(const char *name, const char *source, const char *n
|
||||||
static void testSerializeCorrupt(const char *name);
|
static void testSerializeCorrupt(const char *name);
|
||||||
static void testSharedFormScope(const char *name);
|
static void testSharedFormScope(const char *name);
|
||||||
static void testSleepPumpsEvents(const char *name);
|
static void testSleepPumpsEvents(const char *name);
|
||||||
|
static void testSliceClock(void);
|
||||||
static void testStrAppend(void);
|
static void testStrAppend(void);
|
||||||
static void testSubEq(const char *name, const char *source, const char *subName, const char *expected);
|
static void testSubEq(const char *name, const char *source, const char *subName, const char *expected);
|
||||||
static void testUiEq(const char *name, const char *source, const char *expected);
|
static void testUiEq(const char *name, const char *source, const char *expected);
|
||||||
|
|
@ -314,6 +332,12 @@ static void uiStubSetEvent(void *ctx, void *ctrlRef, const char *eventName, cons
|
||||||
static void uiStubSetProp(void *ctx, void *ctrlRef, const char *propName, BasValueT value);
|
static void uiStubSetProp(void *ctx, void *ctrlRef, const char *propName, BasValueT value);
|
||||||
static void uiStubShowForm(void *ctx, void *formRef, bool modal);
|
static void uiStubShowForm(void *ctx, void *formRef, bool modal);
|
||||||
static void uiStubUnloadForm(void *ctx, void *formRef);
|
static void uiStubUnloadForm(void *ctx, void *formRef);
|
||||||
|
static void unitCountStub(void *ctx);
|
||||||
|
|
||||||
|
|
||||||
|
// testSliceClock's slice clock (printTickStub moves it) and its prints
|
||||||
|
static volatile uint32_t sSliceTicks = 0;
|
||||||
|
static int32_t sSlicePrints = 0;
|
||||||
|
|
||||||
|
|
||||||
static const BasUiCallbacksT sUiStubs = {
|
static const BasUiCallbacksT sUiStubs = {
|
||||||
|
|
@ -482,6 +506,31 @@ static int32_t deviceStrigStub(void *ctx, int32_t n) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// testDirPumps' files: TS_DIR_PUMP_FILES that do not match and one that
|
||||||
|
// does, made (empty) or removed.
|
||||||
|
static void dirPumpFiles(bool make) {
|
||||||
|
char path[TEST_DETAIL_LEN];
|
||||||
|
|
||||||
|
for (int32_t i = 0; i <= TS_DIR_PUMP_FILES; i++) {
|
||||||
|
if (i < TS_DIR_PUMP_FILES) {
|
||||||
|
snprintf(path, sizeof(path), TS_DIR_PUMP_DIR "/f%03d.dat", (int)i);
|
||||||
|
} else {
|
||||||
|
snprintf(path, sizeof(path), TS_DIR_PUMP_DIR "/match.zzz");
|
||||||
|
}
|
||||||
|
|
||||||
|
FILE *f = make ? fopen(path, "w") : NULL;
|
||||||
|
|
||||||
|
if (f) {
|
||||||
|
fclose(f);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!make) {
|
||||||
|
remove(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// DoEvents stub that tells the VM to stop (host shutting down).
|
// DoEvents stub that tells the VM to stop (host shutting down).
|
||||||
static bool doEventsAbortStub(void *ctx) {
|
static bool doEventsAbortStub(void *ctx) {
|
||||||
(void)ctx;
|
(void)ctx;
|
||||||
|
|
@ -573,6 +622,38 @@ static void handEmitU16(HandCodeT *h, uint16_t v) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// testIniCache's DoEvents: the nth call rewrites the file -- the same size
|
||||||
|
// and time, then another size, then the same size at the time now.
|
||||||
|
static bool iniRewriteStub(void *ctx) {
|
||||||
|
static const char *texts[] = { "[A]\nk=2\n", "[A]\nk=33\n", "[A]\nk=44\n" };
|
||||||
|
int32_t *calls = (int32_t *)ctx;
|
||||||
|
|
||||||
|
if (*calls >= (int32_t)(sizeof(texts) / sizeof(texts[0]))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
FILE *f = fopen(TS_INI_FILE, "w");
|
||||||
|
|
||||||
|
if (!f) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
fputs(texts[*calls], f);
|
||||||
|
fclose(f);
|
||||||
|
|
||||||
|
if (*calls < 2) {
|
||||||
|
struct utimbuf settled;
|
||||||
|
|
||||||
|
settled.actime = time(NULL) - TS_INI_SETTLED_SEC;
|
||||||
|
settled.modtime = settled.actime;
|
||||||
|
utime(TS_INI_FILE, &settled);
|
||||||
|
}
|
||||||
|
|
||||||
|
(*calls)++;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// INPUT stub: logs the prompt, answers "42" on the first call and
|
// INPUT stub: logs the prompt, answers "42" on the first call and
|
||||||
// cancels every later one.
|
// cancels every later one.
|
||||||
static bool inputStub(void *ctx, const char *prompt, char *buf, int32_t bufSize) {
|
static bool inputStub(void *ctx, const char *prompt, char *buf, int32_t bufSize) {
|
||||||
|
|
@ -797,6 +878,18 @@ static bool moduleHasOpcode(const BasModuleT *mod, uint8_t op) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// testSliceClock's print callback: each line moves the slice clock.
|
||||||
|
static void printTickStub(void *ctx, const char *text, bool newline) {
|
||||||
|
(void)ctx;
|
||||||
|
(void)text;
|
||||||
|
|
||||||
|
if (newline) {
|
||||||
|
sSliceTicks++;
|
||||||
|
sSlicePrints++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static void reportFail(const char *name, const char *detail) {
|
static void reportFail(const char *name, const char *detail) {
|
||||||
printf("FAIL %s\n", name);
|
printf("FAIL %s\n", name);
|
||||||
|
|
||||||
|
|
@ -1435,6 +1528,26 @@ static void testCompileError(const char *name, const char *source, const char *n
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// basBuildCompile's unit callback runs after every unit compiled (a
|
||||||
|
// statement, or a whole procedure), and the module is the same as
|
||||||
|
// without it.
|
||||||
|
static void testCompileUnits(void) {
|
||||||
|
const char *src = "a = 1\nb = 2\nSUB S\n PRINT 1\n PRINT 2\nEND SUB\nPRINT a + b\n";
|
||||||
|
BasCompileErrorT err;
|
||||||
|
int32_t units = 0;
|
||||||
|
char detail[TEST_DETAIL_LEN];
|
||||||
|
BasModuleT *mod = basBuildCompile(src, (int32_t)strlen(src), false, NULL, NULL, &err, unitCountStub, &units);
|
||||||
|
BasModuleT *plain = basBuildCompile(src, (int32_t)strlen(src), false, NULL, NULL, &err, NULL, NULL);
|
||||||
|
|
||||||
|
snprintf(detail, sizeof(detail), "units=%d", (int)units);
|
||||||
|
expectTrue("compile-unit-callback", mod && plain && units == TS_COMPILE_UNITS && mod->codeLen == plain->codeLen && memcmp(mod->code, plain->code, (size_t)mod->codeLen) == 0, detail);
|
||||||
|
basModuleFree(mod);
|
||||||
|
basModuleFree(plain);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Breakpoints, step into/over/out, run to cursor, and the runSubLoop
|
||||||
|
// pause/resume/abort protocol used by the IDE debugger.
|
||||||
static void testDebuggerApi(void) {
|
static void testDebuggerApi(void) {
|
||||||
const char *source =
|
const char *source =
|
||||||
"DIM x AS INTEGER\n"
|
"DIM x AS INTEGER\n"
|
||||||
|
|
@ -1623,6 +1736,9 @@ static void testDebuggerApi(void) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Debug-info side channel: serialize the debugVars table, wipe it on a
|
||||||
|
// second compile of the same source, deserialize, and compare. Also
|
||||||
|
// drives the truncated-stream rejection paths.
|
||||||
static void testDebugInfoRoundTrip(const char *name) {
|
static void testDebugInfoRoundTrip(const char *name) {
|
||||||
const char *source =
|
const char *source =
|
||||||
"DIM g AS INTEGER\n"
|
"DIM g AS INTEGER\n"
|
||||||
|
|
@ -1728,9 +1844,6 @@ static void testDebugInfoRoundTrip(const char *name) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Debug-info side channel: serialize the debugVars table, wipe it on a
|
|
||||||
// second compile of the same source, deserialize, and compare. Also
|
|
||||||
// drives the truncated-stream rejection paths.
|
|
||||||
// BEEP, SOUND, PLAY, PLAY(n), STICK, STRIG and FRAMEWAIT reach the device
|
// BEEP, SOUND, PLAY, PLAY(n), STICK, STRIG and FRAMEWAIT reach the device
|
||||||
// callbacks with QBasic's ranges checked, and wait (pumping events) while
|
// callbacks with QBasic's ranges checked, and wait (pumping events) while
|
||||||
// the host says so.
|
// the host says so.
|
||||||
|
|
@ -1775,8 +1888,42 @@ static void testDeviceStatements(void) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Breakpoints, step into/over/out, run to cursor, and the runSubLoop
|
// Dir$ lets the program's events run every BAS_DIR_PUMP_ENTRIES entries it
|
||||||
// pause/resume/abort protocol used by the IDE debugger.
|
// passes over, and halts the program when they say stop.
|
||||||
|
static void testDirPumps(void) {
|
||||||
|
mkdir(TS_DIR_PUMP_DIR, 0755);
|
||||||
|
dirPumpFiles(true);
|
||||||
|
|
||||||
|
BasModuleT *mod = compileSource("dir-pumps", "n = 0\nf$ = DIR$(\"" TS_DIR_PUMP_DIR "/*.zzz\")\nDO WHILE f$ <> \"\"\n n = n + 1\n f$ = DIR$\nLOOP\nPRINT n\n");
|
||||||
|
|
||||||
|
for (int32_t stop = 0; mod && stop <= 1; stop++) {
|
||||||
|
BasVmT *vm = basVmCreate();
|
||||||
|
CaptureT cap;
|
||||||
|
int32_t countdown = stop ? 0 : TS_PUMPS_MANY;
|
||||||
|
char detail[TEST_DETAIL_LEN];
|
||||||
|
|
||||||
|
captureReset(&cap);
|
||||||
|
basVmLoadModule(vm, mod);
|
||||||
|
basVmSetPrintCallback(vm, capturePrint, &cap);
|
||||||
|
basVmSetDoEventsCallback(vm, doEventsCountdownStub, &countdown);
|
||||||
|
|
||||||
|
BasVmResultE r = basVmRun(vm);
|
||||||
|
|
||||||
|
snprintf(detail, sizeof(detail), "r=%d pumps=%d out=[%s]", (int)r, (int)(TS_PUMPS_MANY - countdown), cap.buf);
|
||||||
|
|
||||||
|
if (stop) {
|
||||||
|
expectTrue("dir-pumps-stop-halts", r == BAS_VM_HALTED && cap.len == 0, detail);
|
||||||
|
} else {
|
||||||
|
expectTrue("dir-pumps-pass-over", strcmp(cap.buf, "1 \n") == 0 && TS_PUMPS_MANY - countdown >= TS_DIR_PUMPS_MIN, detail);
|
||||||
|
}
|
||||||
|
|
||||||
|
basVmDestroy(vm);
|
||||||
|
}
|
||||||
|
|
||||||
|
basModuleFree(mod);
|
||||||
|
dirPumpFiles(false);
|
||||||
|
rmdir(TS_DIR_PUMP_DIR);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static void testDispatchEq(const char *name, const TestDispatchT *d, const char *expected) {
|
static void testDispatchEq(const char *name, const TestDispatchT *d, const char *expected) {
|
||||||
|
|
@ -2390,6 +2537,53 @@ static void testHandRunError(const char *name, const HandCodeT *h, BasStringT **
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// IniRead keeps the file parsed while its size and time are the same and
|
||||||
|
// it is older than a FAT time can tell apart; a new size or a new time
|
||||||
|
// reads it again.
|
||||||
|
static void testIniCache(void) {
|
||||||
|
FILE *f = fopen(TS_INI_FILE, "w");
|
||||||
|
|
||||||
|
if (f) {
|
||||||
|
fputs("[A]\nk=1\n", f);
|
||||||
|
fclose(f);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct utimbuf settled;
|
||||||
|
|
||||||
|
settled.actime = time(NULL) - TS_INI_SETTLED_SEC;
|
||||||
|
settled.modtime = settled.actime;
|
||||||
|
utime(TS_INI_FILE, &settled);
|
||||||
|
|
||||||
|
BasModuleT *mod = compileSource("ini-cache",
|
||||||
|
"PRINT INIREAD(\"" TS_INI_FILE "\", \"A\", \"k\", \"?\")\n"
|
||||||
|
"DOEVENTS\n"
|
||||||
|
"PRINT INIREAD(\"" TS_INI_FILE "\", \"A\", \"k\", \"?\")\n"
|
||||||
|
"DOEVENTS\n"
|
||||||
|
"PRINT INIREAD(\"" TS_INI_FILE "\", \"A\", \"k\", \"?\")\n"
|
||||||
|
"DOEVENTS\n"
|
||||||
|
"PRINT INIREAD(\"" TS_INI_FILE "\", \"A\", \"k\", \"?\")\n");
|
||||||
|
|
||||||
|
if (mod) {
|
||||||
|
BasVmT *vm = basVmCreate();
|
||||||
|
CaptureT cap;
|
||||||
|
int32_t calls = 0;
|
||||||
|
char detail[TEST_DETAIL_LEN];
|
||||||
|
|
||||||
|
captureReset(&cap);
|
||||||
|
basVmLoadModule(vm, mod);
|
||||||
|
basVmSetPrintCallback(vm, capturePrint, &cap);
|
||||||
|
basVmSetDoEventsCallback(vm, iniRewriteStub, &calls);
|
||||||
|
basVmRun(vm);
|
||||||
|
snprintf(detail, sizeof(detail), "calls=%d out=[%s]", (int)calls, cap.buf);
|
||||||
|
expectTrue("ini-cache-kept-and-reread", calls == 3 && strcmp(cap.buf, "1\n1\n33\n44\n") == 0, detail);
|
||||||
|
basVmDestroy(vm);
|
||||||
|
basModuleFree(mod);
|
||||||
|
}
|
||||||
|
|
||||||
|
remove(TS_INI_FILE);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// INPUT through the host callback: prompt text, an accepted line, and a
|
// INPUT through the host callback: prompt text, an accepted line, and a
|
||||||
// cancelled read (empty result).
|
// cancelled read (empty result).
|
||||||
static void testInputCallback(const char *name) {
|
static void testInputCallback(const char *name) {
|
||||||
|
|
@ -3635,6 +3829,49 @@ static void testSleepPumpsEvents(const char *name) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// The slice clock: a slice ends once the host's clock has moved its span,
|
||||||
|
// at the first instruction run out of line after that, however few steps
|
||||||
|
// it has run -- here each PRINT line moves the clock one count, as a long
|
||||||
|
// native call would. Without a clock the same slice runs to its steps.
|
||||||
|
static void testSliceClock(void) {
|
||||||
|
BasModuleT *mod = compileSource("slice-clock", "DIM i AS LONG\nFOR i = 1 TO 1000000\n PRINT i\nNEXT i\n");
|
||||||
|
|
||||||
|
if (!mod) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
BasVmT *vm = basVmCreate();
|
||||||
|
char detail[TEST_DETAIL_LEN];
|
||||||
|
|
||||||
|
basVmLoadModule(vm, mod);
|
||||||
|
basVmSetPrintCallback(vm, printTickStub, NULL);
|
||||||
|
basVmSetStepLimit(vm, SLICE_STEPS);
|
||||||
|
basVmSetSliceClock(vm, &sSliceTicks, SLICE_SPAN);
|
||||||
|
sSliceTicks = 0;
|
||||||
|
sSlicePrints = 0;
|
||||||
|
|
||||||
|
BasVmResultE r = basVmRun(vm);
|
||||||
|
|
||||||
|
snprintf(detail, sizeof(detail), "r=%d prints=%d", (int)r, (int)sSlicePrints);
|
||||||
|
expectTrue("slice-clock-ends-slice", r == BAS_VM_STEP_LIMIT && sSlicePrints == SLICE_SPAN, detail);
|
||||||
|
|
||||||
|
// The next slice measures from its own start
|
||||||
|
sSlicePrints = 0;
|
||||||
|
r = basVmRun(vm);
|
||||||
|
snprintf(detail, sizeof(detail), "r=%d prints=%d", (int)r, (int)sSlicePrints);
|
||||||
|
expectTrue("slice-clock-next-slice", r == BAS_VM_STEP_LIMIT && sSlicePrints == SLICE_SPAN, detail);
|
||||||
|
|
||||||
|
basVmSetSliceClock(vm, NULL, 0);
|
||||||
|
sSlicePrints = 0;
|
||||||
|
r = basVmRun(vm);
|
||||||
|
snprintf(detail, sizeof(detail), "r=%d prints=%d", (int)r, (int)sSlicePrints);
|
||||||
|
expectTrue("slice-clock-none-steps", r == BAS_VM_STEP_LIMIT && sSlicePrints > SLICE_SPAN, detail);
|
||||||
|
|
||||||
|
basVmDestroy(vm);
|
||||||
|
basModuleFree(mod);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// "s = s + x" / "s = s & x" on a STRING variable compile to OP_STR_APPEND
|
// "s = s + x" / "s = s & x" on a STRING variable compile to OP_STR_APPEND
|
||||||
// (in-place growth); anything whose operand could observe or change the
|
// (in-place growth); anything whose operand could observe or change the
|
||||||
// variable keeps the ordinary load/concat/store form. Both forms must
|
// variable keeps the ordinary load/concat/store form. Both forms must
|
||||||
|
|
@ -4349,6 +4586,12 @@ static void uiStubUnloadForm(void *ctx, void *formRef) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// testCompileUnits' unit callback: counts.
|
||||||
|
static void unitCountStub(void *ctx) {
|
||||||
|
(*(int32_t *)ctx)++;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
int main(void) {
|
int main(void) {
|
||||||
printf("DVX BASIC Regression Suite\n");
|
printf("DVX BASIC Regression Suite\n");
|
||||||
printf("==========================\n");
|
printf("==========================\n");
|
||||||
|
|
@ -5136,6 +5379,20 @@ int main(void) {
|
||||||
"IF t >= 0 THEN PRINT \"ok\"\n",
|
"IF t >= 0 THEN PRINT \"ok\"\n",
|
||||||
"ok\n");
|
"ok\n");
|
||||||
|
|
||||||
|
// --- TIMER keeps rising in fine steps: readings in a busy loop never
|
||||||
|
// go back, and some differ by less than a 55 ms clock tick ---
|
||||||
|
TEST_EQ("timer-fine",
|
||||||
|
"DIM a AS DOUBLE\nDIM b AS DOUBLE\nDIM i AS LONG\nDIM fine AS LONG\nDIM back AS LONG\n"
|
||||||
|
"a = TIMER\n"
|
||||||
|
"FOR i = 1 TO 20000\n"
|
||||||
|
" b = TIMER\n"
|
||||||
|
" IF b < a AND a - b < 1 THEN back = back + 1\n"
|
||||||
|
" IF b > a AND b - a < 0.05 THEN fine = fine + 1\n"
|
||||||
|
" a = b\n"
|
||||||
|
"NEXT i\n"
|
||||||
|
"PRINT back; fine > 0\n",
|
||||||
|
"0 True \n");
|
||||||
|
|
||||||
// --- Integer overflow promotes to LONG ---
|
// --- Integer overflow promotes to LONG ---
|
||||||
TEST_EQ("int-to-long-promotion",
|
TEST_EQ("int-to-long-promotion",
|
||||||
"DIM a AS INTEGER\nDIM b AS INTEGER\n"
|
"DIM a AS INTEGER\nDIM b AS INTEGER\n"
|
||||||
|
|
@ -7777,9 +8034,48 @@ int main(void) {
|
||||||
"KILL \"ts_dird/f1.tmp\"\nRMDIR \"ts_dird/sub\"\nRMDIR \"ts_dird\"\n",
|
"KILL \"ts_dird/f1.tmp\"\nRMDIR \"ts_dird/sub\"\nRMDIR \"ts_dird\"\n",
|
||||||
"[f1.tmp]\n");
|
"[f1.tmp]\n");
|
||||||
|
|
||||||
|
// FileCopy copies a file of many pieces whole (each piece may let the
|
||||||
|
// program's events run).
|
||||||
|
TEST_EQ("filecopy-many-pieces",
|
||||||
|
"OPEN \"ts_fc.tmp\" FOR OUTPUT AS #1\n"
|
||||||
|
"FOR i = 1 TO 2000\n PRINT #1, \"line\"; i; STRING$(40, \"x\")\nNEXT i\n"
|
||||||
|
"CLOSE #1\n"
|
||||||
|
"FILECOPY \"ts_fc.tmp\", \"ts_fc2.tmp\"\n"
|
||||||
|
"PRINT FILELEN(\"ts_fc.tmp\") = FILELEN(\"ts_fc2.tmp\"); FILELEN(\"ts_fc2.tmp\") > 65536\n"
|
||||||
|
"OPEN \"ts_fc2.tmp\" FOR INPUT AS #1\n"
|
||||||
|
"DO WHILE NOT EOF(1)\n LINE INPUT #1, l$\nLOOP\n"
|
||||||
|
"CLOSE #1\n"
|
||||||
|
"PRINT LEFT$(l$, 9)\n"
|
||||||
|
"KILL \"ts_fc.tmp\"\nKILL \"ts_fc2.tmp\"\n",
|
||||||
|
"True True \nline2000 \n");
|
||||||
|
|
||||||
|
// Keyed Collection lookups through the key index: built on the first
|
||||||
|
// lookup, kept as keys are added (and rebuilt as it fills), rebuilt
|
||||||
|
// after a removal or an insert before the end; keys ignore case.
|
||||||
|
TEST_EQ("collection-key-index",
|
||||||
|
"DIM c AS NEW Collection\nDIM i AS LONG\nDIM s AS LONG\n"
|
||||||
|
"FOR i = 1 TO 5\n c.Add i, \"k\" & i\nNEXT i\n"
|
||||||
|
"PRINT c(\"k3\")\n"
|
||||||
|
"FOR i = 6 TO 100\n c.Add i, \"k\" & i\nNEXT i\n"
|
||||||
|
"FOR i = 1 TO 100\n s = s + c(\"K\" & i)\nNEXT i\n"
|
||||||
|
"PRINT s; c.Count\n"
|
||||||
|
"c.Remove \"k50\"\nc.Remove 1\n"
|
||||||
|
"PRINT c.Count; c(\"k51\"); c(1); c(49)\n"
|
||||||
|
"c.Add 777, \"new\", 1\n"
|
||||||
|
"PRINT c(1); c(\"NEW\"); c(\"k100\"); c(\"k2\")\n"
|
||||||
|
"ON ERROR GOTO EH\n"
|
||||||
|
"c.Add 1, \"K2\"\n"
|
||||||
|
"x = c(\"k50\")\n"
|
||||||
|
"PRINT \"end\"\nEND\n"
|
||||||
|
"EH:\nPRINT \"err\"; ERR\nRESUME NEXT\n",
|
||||||
|
"3 \n5050 100 \n98 51 2 51 \n777 777 100 2 \nerr457 \nerr5 \nend\n");
|
||||||
|
|
||||||
// SLEEP keeps pumping the host's events and stops when told to.
|
// SLEEP keeps pumping the host's events and stops when told to.
|
||||||
testSleepPumpsEvents("sleep-pumps-events");
|
testSleepPumpsEvents("sleep-pumps-events");
|
||||||
|
testSliceClock();
|
||||||
testDeviceStatements();
|
testDeviceStatements();
|
||||||
|
testDirPumps();
|
||||||
|
testIniCache();
|
||||||
testStrAppend();
|
testStrAppend();
|
||||||
testObfuscateCaption("obfuscate-caption-kept");
|
testObfuscateCaption("obfuscate-caption-kept");
|
||||||
testObfuscateKeepsNames("obfuscate-keeps-names-name", "SUB Total_Click\n PRINT Total.Name\nEND SUB\n");
|
testObfuscateKeepsNames("obfuscate-keeps-names-name", "SUB Total_Click\n PRINT Total.Name\nEND SUB\n");
|
||||||
|
|
@ -7799,6 +8095,7 @@ int main(void) {
|
||||||
testHandBattery();
|
testHandBattery();
|
||||||
testOpcodeGuards();
|
testOpcodeGuards();
|
||||||
testBasBuild();
|
testBasBuild();
|
||||||
|
testCompileUnits();
|
||||||
|
|
||||||
// Coverage battery: debugger, externs, serialization, values API.
|
// Coverage battery: debugger, externs, serialization, values API.
|
||||||
testDebuggerApi();
|
testDebuggerApi();
|
||||||
|
|
|
||||||
|
|
@ -1115,7 +1115,11 @@ static void setupMainWindow(void) {
|
||||||
MenuT *viewMenu = wmAddMenu(bar, "&View");
|
MenuT *viewMenu = wmAddMenu(bar, "&View");
|
||||||
|
|
||||||
if (viewMenu) {
|
if (viewMenu) {
|
||||||
|
// The terminal widget is part of Communications
|
||||||
|
if (dvxAnsiTermApi()) {
|
||||||
wmAddMenuItem(viewMenu, "&Terminal", CMD_VIEW_TERM);
|
wmAddMenuItem(viewMenu, "&Terminal", CMD_VIEW_TERM);
|
||||||
|
}
|
||||||
|
|
||||||
wmAddMenuItem(viewMenu, "&Controls", CMD_VIEW_CTRL);
|
wmAddMenuItem(viewMenu, "&Controls", CMD_VIEW_CTRL);
|
||||||
wmAddMenuSeparator(viewMenu);
|
wmAddMenuSeparator(viewMenu);
|
||||||
wmAddMenuCheckItem(viewMenu, "Tool&bar", CMD_VIEW_TOOLBAR, true);
|
wmAddMenuCheckItem(viewMenu, "Tool&bar", CMD_VIEW_TOOLBAR, true);
|
||||||
|
|
@ -1222,7 +1226,12 @@ static void setupMainWindow(void) {
|
||||||
// supported attributes (bold, reverse, blink), 8+8 colors, background
|
// supported attributes (bold, reverse, blink), 8+8 colors, background
|
||||||
// colors, and CP437 box-drawing/graphic characters. The terminal is
|
// colors, and CP437 box-drawing/graphic characters. The terminal is
|
||||||
// minimized on creation to avoid cluttering the initial demo view.
|
// minimized on creation to avoid cluttering the initial demo view.
|
||||||
|
// Nothing without the terminal widget (part of Communications).
|
||||||
static void setupTerminalWindow(void) {
|
static void setupTerminalWindow(void) {
|
||||||
|
if (!dvxAnsiTermApi()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
WindowT *win = dvxCreateWindow(sAc, "ANSI Terminal", DEMO_TERM_X, DEMO_TERM_Y, DEMO_TERM_W, DEMO_TERM_H, true);
|
WindowT *win = dvxCreateWindow(sAc, "ANSI Terminal", DEMO_TERM_X, DEMO_TERM_Y, DEMO_TERM_W, DEMO_TERM_H, true);
|
||||||
|
|
||||||
if (!win) {
|
if (!win) {
|
||||||
|
|
|
||||||
|
|
@ -1,24 +1,24 @@
|
||||||
// The MIT License (MIT)
|
# The MIT License (MIT)
|
||||||
//
|
#
|
||||||
// Copyright (C) 2026 Scott Duensing
|
# Copyright (C) 2026 Scott Duensing
|
||||||
//
|
#
|
||||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
// of this software and associated documentation files (the "Software"), to
|
# of this software and associated documentation files (the "Software"), to
|
||||||
// deal in the Software without restriction, including without limitation the
|
# deal in the Software without restriction, including without limitation the
|
||||||
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||||
// sell copies of the Software, and to permit persons to whom the Software is
|
# sell copies of the Software, and to permit persons to whom the Software is
|
||||||
// furnished to do so, subject to the following conditions:
|
# furnished to do so, subject to the following conditions:
|
||||||
//
|
#
|
||||||
// The above copyright notice and this permission notice shall be included in
|
# The above copyright notice and this permission notice shall be included in
|
||||||
// all copies or substantial portions of the Software.
|
# all copies or substantial portions of the Software.
|
||||||
//
|
#
|
||||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||||
// IN THE SOFTWARE.
|
# IN THE SOFTWARE.
|
||||||
|
|
||||||
# DVX Demo help. Compiled to dvxdemo.hlp next to dvxdemo.app; F1 and
|
# DVX Demo help. Compiled to dvxdemo.hlp next to dvxdemo.app; F1 and
|
||||||
# Help > Contents in the DVX Information window open it.
|
# Help > Contents in the DVX Information window open it.
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,25 @@
|
||||||
|
# The MIT License (MIT)
|
||||||
|
#
|
||||||
|
# Copyright (C) 2026 Scott Duensing
|
||||||
|
#
|
||||||
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
# of this software and associated documentation files (the "Software"), to
|
||||||
|
# deal in the Software without restriction, including without limitation the
|
||||||
|
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||||
|
# sell copies of the Software, and to permit persons to whom the Software is
|
||||||
|
# furnished to do so, subject to the following conditions:
|
||||||
|
#
|
||||||
|
# The above copyright notice and this permission notice shall be included in
|
||||||
|
# all copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||||
|
# IN THE SOFTWARE.
|
||||||
|
|
||||||
# dvxdemo.res -- Resource manifest for DVX Demo
|
# dvxdemo.res -- Resource manifest for DVX Demo
|
||||||
icon32 icon icon32.bmp
|
icon32 icon icon32.bmp
|
||||||
name text "DVX Demo"
|
name text "DVX Demo"
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,25 @@
|
||||||
|
# The MIT License (MIT)
|
||||||
|
#
|
||||||
|
# Copyright (C) 2026 Scott Duensing
|
||||||
|
#
|
||||||
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
# of this software and associated documentation files (the "Software"), to
|
||||||
|
# deal in the Software without restriction, including without limitation the
|
||||||
|
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||||
|
# sell copies of the Software, and to permit persons to whom the Software is
|
||||||
|
# furnished to do so, subject to the following conditions:
|
||||||
|
#
|
||||||
|
# The above copyright notice and this permission notice shall be included in
|
||||||
|
# all copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||||
|
# IN THE SOFTWARE.
|
||||||
|
|
||||||
# dvxhelp.res -- Resource manifest for DVX Help Viewer
|
# dvxhelp.res -- Resource manifest for DVX Help Viewer
|
||||||
icon32 icon icon32.bmp
|
icon32 icon icon32.bmp
|
||||||
name text "DVX Help"
|
name text "DVX Help"
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,9 @@
|
||||||
' the variables of the form whose code it is, and a finished job shows
|
' the variables of the form whose code it is, and a finished job shows
|
||||||
' the main window's folder again.
|
' the main window's folder again.
|
||||||
'
|
'
|
||||||
' The Properties dialog leaves its answer here too.
|
' The Properties dialog leaves its answer here too, and counts the size
|
||||||
|
' of the selection here: its timer works on a measure job a slice at a
|
||||||
|
' time (fmProps.frm), so a big folder does not stop DVX.
|
||||||
|
|
||||||
OPTION EXPLICIT
|
OPTION EXPLICIT
|
||||||
|
|
||||||
|
|
@ -66,12 +68,24 @@ DIM SHARED fmJobCount AS LONG
|
||||||
DIM SHARED fmPropsAccepted AS BOOLEAN
|
DIM SHARED fmPropsAccepted AS BOOLEAN
|
||||||
DIM SHARED fmPropsAttr AS LONG
|
DIM SHARED fmPropsAttr AS LONG
|
||||||
|
|
||||||
|
' Its count of the selection: the measure job (0 once done), the selected
|
||||||
|
' items that are folders, and whether one could not be found
|
||||||
|
DIM SHARED fmPropsJob AS LONG
|
||||||
|
DIM SHARED fmPropsFolders AS LONG
|
||||||
|
DIM SHARED fmPropsMissing AS BOOLEAN
|
||||||
|
|
||||||
|
|
||||||
SUB ShowError(BYVAL title AS STRING, BYVAL message AS STRING)
|
SUB ShowError(BYVAL title AS STRING, BYVAL message AS STRING)
|
||||||
MsgBox message, vbOKOnly + vbCritical, title
|
MsgBox message, vbOKOnly + vbCritical, title
|
||||||
END SUB
|
END SUB
|
||||||
|
|
||||||
|
|
||||||
|
' "1 file", "3 files": count things, thing in the singular.
|
||||||
|
FUNCTION CountOf$(BYVAL count AS LONG, BYVAL thing AS STRING) AS STRING
|
||||||
|
CountOf$ = CStr(count) & " " & thing & IIF(count = 1, "", "s")
|
||||||
|
END FUNCTION
|
||||||
|
|
||||||
|
|
||||||
' The running job whose window is called windowName, or Nothing.
|
' The running job whose window is called windowName, or Nothing.
|
||||||
FUNCTION FindJob(BYVAL windowName AS STRING) AS FmJob
|
FUNCTION FindJob(BYVAL windowName AS STRING) AS FmJob
|
||||||
DIM job AS FmJob
|
DIM job AS FmJob
|
||||||
|
|
@ -213,6 +227,44 @@ END SUB
|
||||||
' Progress line, file count and bar of the job's window.
|
' Progress line, file count and bar of the job's window.
|
||||||
SUB JobShowProgress(BYVAL job AS FmJob)
|
SUB JobShowProgress(BYVAL job AS FmJob)
|
||||||
job.Window.lblWhat.Caption = job.Verb & " " & FileOpsJobCurrent$(job.Handle)
|
job.Window.lblWhat.Caption = job.Verb & " " & FileOpsJobCurrent$(job.Handle)
|
||||||
job.Window.lblCounts.Caption = CStr(FileOpsJobFilesDone(job.Handle)) & " of " & CStr(FileOpsJobFilesTotal(job.Handle)) & " files"
|
job.Window.lblCounts.Caption = CStr(FileOpsJobFilesDone(job.Handle)) & " of " & CountOf$(FileOpsJobFilesTotal(job.Handle), "file")
|
||||||
job.Window.pbJob.Value = FileOpsJobPercent(job.Handle)
|
job.Window.pbJob.Value = FileOpsJobPercent(job.Handle)
|
||||||
END SUB
|
END SUB
|
||||||
|
|
||||||
|
|
||||||
|
' ============================================================
|
||||||
|
' The Properties dialog's count
|
||||||
|
' ============================================================
|
||||||
|
|
||||||
|
' The size and contents of the selection in the Properties dialog: so far
|
||||||
|
' while counting, then (done) all of it, the job freed and the timer
|
||||||
|
' stopped. The dialog grows when a line gets longer.
|
||||||
|
SUB PropsShowCount(BYVAL done AS BOOLEAN)
|
||||||
|
DIM size AS STRING
|
||||||
|
DIM contains AS STRING
|
||||||
|
DIM grow AS BOOLEAN
|
||||||
|
|
||||||
|
size = "Size: " & FileOpsFormatSize$(FileOpsJobBytesTotal(fmPropsJob)) & " bytes"
|
||||||
|
contains = "Contains: " & CountOf$(FileOpsJobFilesTotal(fmPropsJob), "file") & ", " & CountOf$(FileOpsJobFoldersTotal(fmPropsJob) + fmPropsFolders, "folder")
|
||||||
|
|
||||||
|
IF NOT done THEN
|
||||||
|
size = size & " so far"
|
||||||
|
contains = contains & " so far"
|
||||||
|
ELSEIF fmPropsMissing OR FileOpsJobUnread(fmPropsJob) > 0 THEN
|
||||||
|
size = size & " (some items could not be read)"
|
||||||
|
END IF
|
||||||
|
|
||||||
|
grow = Len(size) > Len(PropsForm.lblSize.Caption) OR Len(contains) > Len(PropsForm.lblContains.Caption)
|
||||||
|
PropsForm.lblSize.Caption = size
|
||||||
|
PropsForm.lblContains.Caption = contains
|
||||||
|
|
||||||
|
IF grow THEN
|
||||||
|
PropsForm.AutoSize = True
|
||||||
|
END IF
|
||||||
|
|
||||||
|
IF done THEN
|
||||||
|
PropsForm.tmrCount.Enabled = False
|
||||||
|
FileOpsJobFree fmPropsJob
|
||||||
|
fmPropsJob = 0
|
||||||
|
END IF
|
||||||
|
END SUB
|
||||||
|
|
|
||||||
|
|
@ -216,7 +216,7 @@ Files and disks bigger than 2 GB (a FAT32 disk) show their full size.
|
||||||
|
|
||||||
.h1 Properties
|
.h1 Properties
|
||||||
|
|
||||||
Select one or more items and choose File > Properties or press Alt+Enter. The Properties window shows the name, the folder it is in, the total size (for a folder, everything in it) with the number of files and folders, and the date the item was last changed.
|
Select one or more items and choose File > Properties or press Alt+Enter. The Properties window shows the name, the folder it is in, the total size (for a folder, everything in it) with the number of files and folders, and the date the item was last changed. A big folder takes a moment to count: the size and the numbers grow, marked "so far", until the count is done, and you can change the attributes or close the window meanwhile.
|
||||||
|
|
||||||
The Attributes boxes can be changed; click OK to apply them to every selected item, or Cancel to leave them as they were.
|
The Attributes boxes can be changed; click OK to apply them to every selected item, or Cancel to leave them as they were.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -513,14 +513,27 @@ SUB UpdateTitle
|
||||||
END SUB
|
END SUB
|
||||||
|
|
||||||
|
|
||||||
' "N items" or "S of N selected (B bytes)", and the free space of the
|
' The free space of the folder's drive. Asking DOS takes a while on a
|
||||||
' folder's drive.
|
' floppy or a network drive, so it is asked on navigate and refresh only.
|
||||||
|
SUB UpdateFree
|
||||||
|
DIM freeBytes AS DOUBLE
|
||||||
|
|
||||||
|
freeBytes = FileOpsFreeBytes(curFolder)
|
||||||
|
|
||||||
|
IF freeBytes >= 0 THEN
|
||||||
|
lblFree.Caption = FileOpsFormatSize$(freeBytes) & " bytes free"
|
||||||
|
ELSE
|
||||||
|
lblFree.Caption = ""
|
||||||
|
END IF
|
||||||
|
END SUB
|
||||||
|
|
||||||
|
|
||||||
|
' "N items" or "S of N selected (B bytes)".
|
||||||
SUB UpdateStatus
|
SUB UpdateStatus
|
||||||
DIM rows AS STRING
|
DIM rows AS STRING
|
||||||
DIM row AS VARIANT
|
DIM row AS VARIANT
|
||||||
DIM selected AS LONG
|
DIM selected AS LONG
|
||||||
DIM bytes AS DOUBLE
|
DIM bytes AS DOUBLE
|
||||||
DIM freeBytes AS DOUBLE
|
|
||||||
|
|
||||||
rows = fmList.SelectedRows()
|
rows = fmList.SelectedRows()
|
||||||
|
|
||||||
|
|
@ -534,15 +547,7 @@ SUB UpdateStatus
|
||||||
IF selected > 0 THEN
|
IF selected > 0 THEN
|
||||||
fmStatus.Caption = CStr(selected) & " of " & CStr(FileOpsListCount(listing)) & " selected (" & FileOpsFormatSize$(bytes) & " bytes)"
|
fmStatus.Caption = CStr(selected) & " of " & CStr(FileOpsListCount(listing)) & " selected (" & FileOpsFormatSize$(bytes) & " bytes)"
|
||||||
ELSE
|
ELSE
|
||||||
fmStatus.Caption = CStr(FileOpsListCount(listing)) & " items"
|
fmStatus.Caption = CountOf$(FileOpsListCount(listing), "item")
|
||||||
END IF
|
|
||||||
|
|
||||||
freeBytes = FileOpsFreeBytes(curFolder)
|
|
||||||
|
|
||||||
IF freeBytes >= 0 THEN
|
|
||||||
lblFree.Caption = FileOpsFormatSize$(freeBytes) & " bytes free"
|
|
||||||
ELSE
|
|
||||||
lblFree.Caption = ""
|
|
||||||
END IF
|
END IF
|
||||||
END SUB
|
END SUB
|
||||||
|
|
||||||
|
|
@ -611,7 +616,7 @@ FUNCTION Navigate(BYVAL path AS STRING, BYVAL fromTree AS BOOLEAN) AS BOOLEAN
|
||||||
|
|
||||||
fmPath.Text = curFolder
|
fmPath.Text = curFolder
|
||||||
UpdateTitle
|
UpdateTitle
|
||||||
UpdateStatus
|
UpdateFree
|
||||||
|
|
||||||
IF fromTree THEN
|
IF fromTree THEN
|
||||||
item = fmTree.ListIndex
|
item = fmTree.ListIndex
|
||||||
|
|
@ -655,12 +660,13 @@ SUB FmRefresh
|
||||||
|
|
||||||
FreeListing
|
FreeListing
|
||||||
fmList.SetRows ""
|
fmList.SetRows ""
|
||||||
|
UpdateStatus
|
||||||
END IF
|
END IF
|
||||||
|
|
||||||
SelectByName keep
|
SelectByName keep
|
||||||
TreeRebuild
|
TreeRebuild
|
||||||
UpdateTitle
|
UpdateTitle
|
||||||
UpdateStatus
|
UpdateFree
|
||||||
END SUB
|
END SUB
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -727,7 +733,8 @@ END FUNCTION
|
||||||
|
|
||||||
' Add folder's sub-folders (sorted by name) under the tree item parentItem,
|
' Add folder's sub-folders (sorted by name) under the tree item parentItem,
|
||||||
' which has none yet, so they follow it. Returns the item of the one at
|
' which has none yet, so they follow it. Returns the item of the one at
|
||||||
' expandPath, -1 when none.
|
' expandPath, -1 when none. The current folder's come from its listing,
|
||||||
|
' which already holds them, instead of reading the disk again.
|
||||||
FUNCTION TreeAddChildren(BYVAL parentItem AS LONG, BYVAL folder AS STRING, BYVAL expandPath AS STRING) AS LONG
|
FUNCTION TreeAddChildren(BYVAL parentItem AS LONG, BYVAL folder AS STRING, BYVAL expandPath AS STRING) AS LONG
|
||||||
DIM subList AS LONG
|
DIM subList AS LONG
|
||||||
DIM count AS LONG
|
DIM count AS LONG
|
||||||
|
|
@ -737,7 +744,12 @@ FUNCTION TreeAddChildren(BYVAL parentItem AS LONG, BYVAL folder AS STRING, BYVAL
|
||||||
DIM found AS LONG
|
DIM found AS LONG
|
||||||
|
|
||||||
found = -1
|
found = -1
|
||||||
|
|
||||||
|
IF listing <> 0 AND FileOpsPathSame(folder, curFolder) THEN
|
||||||
|
subList = FileOpsListFolders(listing)
|
||||||
|
ELSE
|
||||||
subList = FileOpsList(folder, "")
|
subList = FileOpsList(folder, "")
|
||||||
|
END IF
|
||||||
|
|
||||||
IF subList = 0 THEN
|
IF subList = 0 THEN
|
||||||
TreeAddChildren = found
|
TreeAddChildren = found
|
||||||
|
|
@ -1163,14 +1175,12 @@ END SUB
|
||||||
|
|
||||||
' Size, dates and editable attributes of the selection. Attribute changes
|
' Size, dates and editable attributes of the selection. Attribute changes
|
||||||
' apply to every selected item.
|
' apply to every selected item.
|
||||||
|
' The dialog counts the size itself, from its timer (fmProps.frm), with a
|
||||||
|
' measure job of the selection started here.
|
||||||
SUB CmdProperties
|
SUB CmdProperties
|
||||||
DIM paths AS Collection
|
DIM paths AS Collection
|
||||||
DIM path AS VARIANT
|
DIM path AS VARIANT
|
||||||
DIM first AS STRING
|
DIM first AS STRING
|
||||||
DIM bytes AS DOUBLE
|
|
||||||
DIM files AS LONG
|
|
||||||
DIM folders AS LONG
|
|
||||||
DIM allRead AS BOOLEAN
|
|
||||||
DIM attr AS LONG
|
DIM attr AS LONG
|
||||||
DIM location AS STRING
|
DIM location AS STRING
|
||||||
DIM modified AS STRING
|
DIM modified AS STRING
|
||||||
|
|
@ -1183,16 +1193,15 @@ SUB CmdProperties
|
||||||
EXIT SUB
|
EXIT SUB
|
||||||
END IF
|
END IF
|
||||||
|
|
||||||
allRead = True
|
fmPropsJob = FileOpsJobCreate(FILEOPS_MEASURE, "")
|
||||||
|
fmPropsFolders = 0
|
||||||
|
fmPropsMissing = False
|
||||||
|
|
||||||
FOR EACH path IN paths
|
FOR EACH path IN paths
|
||||||
allRead = FileOpsTreeSize(path) AND allRead
|
IF NOT FileOpsJobAddSource(fmPropsJob, path) THEN
|
||||||
bytes = bytes + FileOpsTreeBytes()
|
fmPropsMissing = True
|
||||||
files = files + FileOpsTreeFiles()
|
ELSEIF FileOpsIsFolder(path) THEN
|
||||||
folders = folders + FileOpsTreeFolders()
|
fmPropsFolders = fmPropsFolders + 1
|
||||||
|
|
||||||
IF FileOpsIsFolder(path) THEN
|
|
||||||
folders = folders + 1
|
|
||||||
END IF
|
END IF
|
||||||
NEXT path
|
NEXT path
|
||||||
|
|
||||||
|
|
@ -1218,14 +1227,7 @@ SUB CmdProperties
|
||||||
END IF
|
END IF
|
||||||
|
|
||||||
PropsForm.lblLocation.Caption = "Location: " & location
|
PropsForm.lblLocation.Caption = "Location: " & location
|
||||||
|
PropsShowCount False
|
||||||
IF allRead THEN
|
|
||||||
PropsForm.lblSize.Caption = "Size: " & FileOpsFormatSize$(bytes) & " bytes"
|
|
||||||
ELSE
|
|
||||||
PropsForm.lblSize.Caption = "Size: " & FileOpsFormatSize$(bytes) & " bytes (some items could not be read)"
|
|
||||||
END IF
|
|
||||||
|
|
||||||
PropsForm.lblContains.Caption = "Contains: " & CStr(files) & " files, " & CStr(folders) & " folders"
|
|
||||||
|
|
||||||
IF paths.Count = 1 AND modified <> "" THEN
|
IF paths.Count = 1 AND modified <> "" THEN
|
||||||
PropsForm.lblModified.Caption = "Modified: " & modified
|
PropsForm.lblModified.Caption = "Modified: " & modified
|
||||||
|
|
@ -1239,9 +1241,12 @@ SUB CmdProperties
|
||||||
PropsForm.chkArchive.Value = (attr AND FILEOPS_ATTR_ARCHIVE) <> 0
|
PropsForm.chkArchive.Value = (attr AND FILEOPS_ATTR_ARCHIVE) <> 0
|
||||||
|
|
||||||
' Fit the dialog to what it now says and centre it, then wait for it
|
' Fit the dialog to what it now says and centre it, then wait for it
|
||||||
|
' while its timer counts
|
||||||
fmPropsAccepted = False
|
fmPropsAccepted = False
|
||||||
PropsForm.AutoSize = True
|
PropsForm.AutoSize = True
|
||||||
PropsForm.Centered = True
|
PropsForm.Centered = True
|
||||||
|
PropsForm.tmrCount.Interval = FM_JOB_TICK
|
||||||
|
PropsForm.tmrCount.Enabled = True
|
||||||
PropsForm.Show vbModal
|
PropsForm.Show vbModal
|
||||||
|
|
||||||
IF NOT fmPropsAccepted THEN
|
IF NOT fmPropsAccepted THEN
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,9 @@ Begin Form PropsForm
|
||||||
Begin Label lblModified
|
Begin Label lblModified
|
||||||
Caption = ""
|
Caption = ""
|
||||||
End
|
End
|
||||||
|
Begin Timer tmrCount
|
||||||
|
Enabled = False
|
||||||
|
End
|
||||||
Begin Frame fraAttributes
|
Begin Frame fraAttributes
|
||||||
Caption = "Attributes"
|
Caption = "Attributes"
|
||||||
Begin CheckBox chkReadOnly
|
Begin CheckBox chkReadOnly
|
||||||
|
|
@ -78,7 +81,8 @@ End
|
||||||
|
|
||||||
' fmProps.frm -- the File Manager's Properties dialog: what the selection
|
' fmProps.frm -- the File Manager's Properties dialog: what the selection
|
||||||
' is (the File Manager fills in the labels and ticks the attributes before
|
' is (the File Manager fills in the labels and ticks the attributes before
|
||||||
' showing it with Show vbModal) and its Read-only, Hidden, System and
|
' showing it with Show vbModal; tmrCount counts its size meanwhile, a
|
||||||
|
' slice of a measure job at a time) and its Read-only, Hidden, System and
|
||||||
' Archive attributes. OK leaves the ticked attributes in fmPropsAttr and
|
' Archive attributes. OK leaves the ticked attributes in fmPropsAttr and
|
||||||
' sets fmPropsAccepted (fileMgr.bas); Cancel, Escape and the close box
|
' sets fmPropsAccepted (fileMgr.bas); Cancel, Escape and the close box
|
||||||
' leave fmPropsAccepted False. Its buttons are the size of the buttons of
|
' leave fmPropsAccepted False. Its buttons are the size of the buttons of
|
||||||
|
|
@ -114,3 +118,15 @@ END SUB
|
||||||
SUB cmdCancel_Click
|
SUB cmdCancel_Click
|
||||||
Unload PropsForm
|
Unload PropsForm
|
||||||
END SUB
|
END SUB
|
||||||
|
|
||||||
|
|
||||||
|
' Closed before the count is done: the count stops.
|
||||||
|
SUB PropsForm_Unload
|
||||||
|
FileOpsJobFree fmPropsJob
|
||||||
|
fmPropsJob = 0
|
||||||
|
END SUB
|
||||||
|
|
||||||
|
|
||||||
|
SUB tmrCount_Timer
|
||||||
|
PropsShowCount FileOpsJobRunFor(fmPropsJob, FM_SLICE_MS) <> FILEOPS_STEP_BUSY
|
||||||
|
END SUB
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue