Bug fixes.
This commit is contained in:
parent
4cdcfe6b8c
commit
60d24c8c33
41 changed files with 4802 additions and 3306 deletions
|
|
@ -147,465 +147,465 @@ img { max-width: 100%; }
|
|||
</ul>
|
||||
<h3>Index</h3>
|
||||
<ul>
|
||||
<li><a href="#ide.overview">DVX BASIC</a></li>
|
||||
<li><a href="#ide.overview">IDE</a></li>
|
||||
<li><a href="#ide.overview">Visual Basic</a></li>
|
||||
<li><a href="#ide.overview">Development Environment</a></li>
|
||||
<li><a href="#ide.menu.file">File Menu</a></li>
|
||||
<li><a href="#ide.menu.file">New Project</a></li>
|
||||
<li><a href="#ide.menu.file">Open Project</a></li>
|
||||
<li><a href="#ide.menu.file">Save Project</a></li>
|
||||
<li><a href="#ide.menu.file">Close Project</a></li>
|
||||
<li><a href="#ide.menu.file">Project Properties</a></li>
|
||||
<li><a href="#ide.menu.file">Add File</a></li>
|
||||
<li><a href="#ide.menu.file">Save File</a></li>
|
||||
<li><a href="#ide.menu.file">Save All</a></li>
|
||||
<li><a href="#ide.menu.file">Remove File</a></li>
|
||||
<li><a href="#ide.menu.file">Exit</a></li>
|
||||
<li><a href="#ide.menu.edit">Edit Menu</a></li>
|
||||
<li><a href="#ide.menu.edit">Cut</a></li>
|
||||
<li><a href="#ide.menu.edit">Copy</a></li>
|
||||
<li><a href="#ide.menu.edit">Paste</a></li>
|
||||
<li><a href="#ide.menu.edit">Select All</a></li>
|
||||
<li><a href="#ide.menu.edit">Delete</a></li>
|
||||
<li><a href="#ide.menu.edit">Find</a></li>
|
||||
<li><a href="#ide.menu.edit">Find Next</a></li>
|
||||
<li><a href="#ide.menu.edit">Replace</a></li>
|
||||
<li><a href="#ide.menu.run">Run Menu</a></li>
|
||||
<li><a href="#ide.menu.run">Run</a></li>
|
||||
<li><a href="#ide.menu.run">Debug</a></li>
|
||||
<li><a href="#ide.menu.run">Run Without Recompile</a></li>
|
||||
<li><a href="#ide.menu.run">Stop</a></li>
|
||||
<li><a href="#ide.menu.run">Step Into</a></li>
|
||||
<li><a href="#ide.menu.run">Step Over</a></li>
|
||||
<li><a href="#ide.menu.run">Step Out</a></li>
|
||||
<li><a href="#ide.menu.run">Run to Cursor</a></li>
|
||||
<li><a href="#ide.menu.run">Toggle Breakpoint</a></li>
|
||||
<li><a href="#ide.menu.run">Clear Output</a></li>
|
||||
<li><a href="#ide.menu.run">Save on Run</a></li>
|
||||
<li><a href="#ide.menu.view">View Menu</a></li>
|
||||
<li><a href="#ide.menu.view">Code View</a></li>
|
||||
<li><a href="#ide.menu.view">Design View</a></li>
|
||||
<li><a href="#ide.menu.view">Toolbar Toggle</a></li>
|
||||
<li><a href="#ide.menu.view">Status Bar Toggle</a></li>
|
||||
<li><a href="#ide.menu.view">Menu Editor</a></li>
|
||||
<li><a href="#ide.menu.window">Window Menu</a></li>
|
||||
<li><a href="#ide.menu.tools">Tools Menu</a></li>
|
||||
<li><a href="#ide.menu.tools">Preferences</a></li>
|
||||
<li><a href="#ide.menu.tools">Debug Layout</a></li>
|
||||
<li><a href="#ide.menu.help">Help Menu</a></li>
|
||||
<li><a href="#ide.menu.help">About</a></li>
|
||||
<li><a href="#ide.toolbar">Toolbar</a></li>
|
||||
<li><a href="#ide.editor">Code Editor</a></li>
|
||||
<li><a href="#ide.editor">Syntax Highlighting</a></li>
|
||||
<li><a href="#ide.editor">Object Dropdown</a></li>
|
||||
<li><a href="#ide.editor">Function Dropdown</a></li>
|
||||
<li><a href="#ide.editor">Line Numbers</a></li>
|
||||
<li><a href="#ide.editor">Auto-indent</a></li>
|
||||
<li><a href="#ide.designer">Form Designer</a></li>
|
||||
<li><a href="#ide.designer">Design Surface</a></li>
|
||||
<li><a href="#ide.designer">Grid Snapping</a></li>
|
||||
<li><a href="#ide.designer">Grab Handles</a></li>
|
||||
<li><a href="#ide.designer">Control Placement</a></li>
|
||||
<li><a href="#ide.designer">WYSIWYG</a></li>
|
||||
<li><a href="#ide.project">Project System</a></li>
|
||||
<li><a href="#ide.project">Project Files</a></li>
|
||||
<li><a href="#ide.project">.dbp Files</a></li>
|
||||
<li><a href="#ide.project">Project Explorer</a></li>
|
||||
<li><a href="#ide.project">Source Map</a></li>
|
||||
<li><a href="#ide.properties">Properties Panel</a></li>
|
||||
<li><a href="#ide.properties">Control Properties</a></li>
|
||||
<li><a href="#ide.properties">Property List</a></li>
|
||||
<li><a href="#ide.properties">Control Tree</a></li>
|
||||
<li><a href="#ide.toolbox">Toolbox</a></li>
|
||||
<li><a href="#ide.toolbox">Controls</a></li>
|
||||
<li><a href="#ide.toolbox">Widget Palette</a></li>
|
||||
<li><a href="#ide.debugger">Debugger</a></li>
|
||||
<li><a href="#ide.debugger">Breakpoints</a></li>
|
||||
<li><a href="#ide.debugger">Stepping</a></li>
|
||||
<li><a href="#ide.debugger">Debug Mode</a></li>
|
||||
<li><a href="#ide.debug.locals">Locals Window</a></li>
|
||||
<li><a href="#ide.debug.locals">Variable Inspection</a></li>
|
||||
<li><a href="#ide.debug.callstack">Call Stack</a></li>
|
||||
<li><a href="#ide.debug.callstack">Stack Trace</a></li>
|
||||
<li><a href="#ide.debug.watch">Watch Window</a></li>
|
||||
<li><a href="#ide.debug.watch">Watch Expressions</a></li>
|
||||
<li><a href="#ide.debug.breakpoints">Breakpoints Window</a></li>
|
||||
<li><a href="#ide.immediate">Immediate Window</a></li>
|
||||
<li><a href="#ide.immediate">REPL</a></li>
|
||||
<li><a href="#ide.immediate">Expression Evaluation</a></li>
|
||||
<li><a href="#ide.immediate">Variable Assignment</a></li>
|
||||
<li><a href="#ide.output">Output Window</a></li>
|
||||
<li><a href="#ide.output">PRINT Output</a></li>
|
||||
<li><a href="#ide.output">Runtime Errors</a></li>
|
||||
<li><a href="#ide.output">Compile Errors</a></li>
|
||||
<li><a href="#ide.findreplace">Find</a></li>
|
||||
<li><a href="#ide.findreplace">Replace</a></li>
|
||||
<li><a href="#ide.findreplace">Find Next</a></li>
|
||||
<li><a href="#ide.findreplace">Search</a></li>
|
||||
<li><a href="#ide.preferences">Preferences</a></li>
|
||||
<li><a href="#ide.preferences">Settings</a></li>
|
||||
<li><a href="#ide.preferences">Tab Width</a></li>
|
||||
<li><a href="#ide.preferences">Option Explicit</a></li>
|
||||
<li><a href="#ide.shortcuts">Keyboard Shortcuts</a></li>
|
||||
<li><a href="#ide.shortcuts">Hotkeys</a></li>
|
||||
<li><a href="#ctrl.frm">.frm</a></li>
|
||||
<li><a href="#ide.menu.help">About</a></li>
|
||||
<li><a href="#lang.func.math">ABS</a></li>
|
||||
<li><a href="#ide.shortcuts">Accelerators</a></li>
|
||||
<li><a href="#lang.datatypes">Data Types</a></li>
|
||||
<li><a href="#lang.datatypes">Integer</a></li>
|
||||
<li><a href="#lang.datatypes">Long</a></li>
|
||||
<li><a href="#lang.datatypes">Single</a></li>
|
||||
<li><a href="#lang.datatypes">Double</a></li>
|
||||
<li><a href="#lang.datatypes">String</a></li>
|
||||
<li><a href="#lang.datatypes">Boolean</a></li>
|
||||
<li><a href="#lang.operators">Operators</a></li>
|
||||
<li><a href="#lang.operators">Precedence</a></li>
|
||||
<li><a href="#ide.menu.file">Add File</a></li>
|
||||
<li><a href="#ctrl.treeview">AddChildItem</a></li>
|
||||
<li><a href="#ctrl.listbox">AddItem</a></li>
|
||||
<li><a href="#ctrl.treeview">AddItem</a></li>
|
||||
<li><a href="#ctrl.data">AddNew</a></li>
|
||||
<li><a href="#ctrl.label">Alignment</a></li>
|
||||
<li><a href="#lang.operators">AND</a></li>
|
||||
<li><a href="#lang.operators">OR</a></li>
|
||||
<li><a href="#lang.operators">NOT</a></li>
|
||||
<li><a href="#lang.operators">XOR</a></li>
|
||||
<li><a href="#lang.operators">EQV</a></li>
|
||||
<li><a href="#lang.operators">IMP</a></li>
|
||||
<li><a href="#lang.operators">MOD</a></li>
|
||||
<li><a href="#lang.statements">Statements</a></li>
|
||||
<li><a href="#lang.statements">REM</a></li>
|
||||
<li><a href="#ctrl.terminal">ANSI Terminal</a></li>
|
||||
<li><a href="#lang.app">App</a></li>
|
||||
<li><a href="#lang.app">App.Config</a></li>
|
||||
<li><a href="#lang.app">App.Data</a></li>
|
||||
<li><a href="#lang.app">App.Path</a></li>
|
||||
<li><a href="#lang.func.string">ASC</a></li>
|
||||
<li><a href="#lang.func.math">ATN</a></li>
|
||||
<li><a href="#ide.editor">Auto-indent</a></li>
|
||||
<li><a href="#ctrl.textarea">AutoIndent</a></li>
|
||||
<li><a href="#ctrl.form">AutoSize</a></li>
|
||||
<li><a href="#ctrl.image">BMP</a></li>
|
||||
<li><a href="#lang.datatypes">Boolean</a></li>
|
||||
<li><a href="#ide.debugger">Breakpoints</a></li>
|
||||
<li><a href="#ide.debug.breakpoints">Breakpoints Window</a></li>
|
||||
<li><a href="#ctrl.button">Button</a></li>
|
||||
<li><a href="#lang.procedures">BYVAL</a></li>
|
||||
<li><a href="#lang.flow">CALL</a></li>
|
||||
<li><a href="#ide.debug.callstack">Call Stack</a></li>
|
||||
<li><a href="#ctrl.picturebox">Canvas</a></li>
|
||||
<li><a href="#ctrl.form">Caption</a></li>
|
||||
<li><a href="#ctrl.label">Caption</a></li>
|
||||
<li><a href="#lang.conditionals">CASE</a></li>
|
||||
<li><a href="#lang.conditionals">CASE ELSE</a></li>
|
||||
<li><a href="#lang.func.conversion">CBOOL</a></li>
|
||||
<li><a href="#lang.func.conversion">CDBL</a></li>
|
||||
<li><a href="#lang.filesystem">CHDIR</a></li>
|
||||
<li><a href="#lang.filesystem">CHDRIVE</a></li>
|
||||
<li><a href="#ctrl.checkbox">CheckBox</a></li>
|
||||
<li><a href="#lang.func.string">CHR$</a></li>
|
||||
<li><a href="#lang.func.conversion">CINT</a></li>
|
||||
<li><a href="#ide.menu.run">Clear Output</a></li>
|
||||
<li><a href="#ctrl.button">Click</a></li>
|
||||
<li><a href="#lang.func.conversion">CLNG</a></li>
|
||||
<li><a href="#lang.fileio">CLOSE</a></li>
|
||||
<li><a href="#ide.menu.file">Close Project</a></li>
|
||||
<li><a href="#ide.editor">Code Editor</a></li>
|
||||
<li><a href="#ide.menu.view">Code View</a></li>
|
||||
<li><a href="#ctrl.combobox">ComboBox</a></li>
|
||||
<li><a href="#lang.runtime">comm.bas</a></li>
|
||||
<li><a href="#ctrl.button">CommandButton</a></li>
|
||||
<li><a href="#lang.runtime">commdlg.bas</a></li>
|
||||
<li><a href="#lang.statements">Comments</a></li>
|
||||
<li><a href="#lang.declarations">DIM</a></li>
|
||||
<li><a href="#lang.declarations">REDIM</a></li>
|
||||
<li><a href="#ctrl.common.props">Common Events</a></li>
|
||||
<li><a href="#ctrl.common.props">Common Methods</a></li>
|
||||
<li><a href="#ctrl.common.props">Common Properties</a></li>
|
||||
<li><a href="#ide.output">Compile Errors</a></li>
|
||||
<li><a href="#lang.declarations">CONST</a></li>
|
||||
<li><a href="#lang.declarations">TYPE</a></li>
|
||||
<li><a href="#lang.declarations">END TYPE</a></li>
|
||||
<li><a href="#ctrl.frame">Container</a></li>
|
||||
<li><a href="#lang.forms">Control Arrays</a></li>
|
||||
<li><a href="#ctrl.arrays">Control Arrays</a></li>
|
||||
<li><a href="#ide.designer">Control Placement</a></li>
|
||||
<li><a href="#ide.properties">Control Properties</a></li>
|
||||
<li><a href="#ide.properties">Control Tree</a></li>
|
||||
<li><a href="#ide.toolbox">Controls</a></li>
|
||||
<li><a href="#ide.menu.edit">Copy</a></li>
|
||||
<li><a href="#lang.func.math">COS</a></li>
|
||||
<li><a href="#lang.forms">CreateControl</a></li>
|
||||
<li><a href="#lang.forms">CreateForm</a></li>
|
||||
<li><a href="#lang.func.conversion">CSNG</a></li>
|
||||
<li><a href="#lang.func.conversion">CSTR</a></li>
|
||||
<li><a href="#ctrl.textarea">CursorLine</a></li>
|
||||
<li><a href="#ide.menu.edit">Cut</a></li>
|
||||
<li><a href="#lang.io">DATA</a></li>
|
||||
<li><a href="#ctrl.data">Data</a></li>
|
||||
<li><a href="#ctrl.databinding">Data Binding</a></li>
|
||||
<li><a href="#lang.datatypes">Data Types</a></li>
|
||||
<li><a href="#ctrl.dbgrid">Data-Bound Grid</a></li>
|
||||
<li><a href="#ctrl.data">Database</a></li>
|
||||
<li><a href="#ctrl.data">DatabaseName</a></li>
|
||||
<li><a href="#ctrl.databinding">DataField</a></li>
|
||||
<li><a href="#ctrl.textbox">DataField</a></li>
|
||||
<li><a href="#ctrl.databinding">DataSource</a></li>
|
||||
<li><a href="#ctrl.textbox">DataSource</a></li>
|
||||
<li><a href="#lang.func.misc">DATE$</a></li>
|
||||
<li><a href="#ctrl.dbgrid">DBGrid</a></li>
|
||||
<li><a href="#ide.menu.run">Debug</a></li>
|
||||
<li><a href="#ide.menu.tools">Debug Layout</a></li>
|
||||
<li><a href="#ide.debugger">Debug Mode</a></li>
|
||||
<li><a href="#ide.debugger">Debugger</a></li>
|
||||
<li><a href="#ctrl.spinbutton">Decimals</a></li>
|
||||
<li><a href="#lang.declarations">DECLARE</a></li>
|
||||
<li><a href="#lang.declarations">DECLARE LIBRARY</a></li>
|
||||
<li><a href="#lang.declarations">SHARED</a></li>
|
||||
<li><a href="#lang.declarations">STATIC</a></li>
|
||||
<li><a href="#lang.declarations">OPTION</a></li>
|
||||
<li><a href="#lang.declarations">OPTION BASE</a></li>
|
||||
<li><a href="#lang.declarations">OPTION COMPARE</a></li>
|
||||
<li><a href="#lang.declarations">OPTION EXPLICIT</a></li>
|
||||
<li><a href="#lang.runtime">DECLARE LIBRARY</a></li>
|
||||
<li><a href="#lang.procedures">DEF FN</a></li>
|
||||
<li><a href="#lang.declarations">DEFDBL</a></li>
|
||||
<li><a href="#lang.declarations">DEFINT</a></li>
|
||||
<li><a href="#lang.declarations">DEFLNG</a></li>
|
||||
<li><a href="#lang.declarations">DEFSNG</a></li>
|
||||
<li><a href="#lang.declarations">DEFDBL</a></li>
|
||||
<li><a href="#lang.declarations">DEFSTR</a></li>
|
||||
<li><a href="#lang.declarations">LET</a></li>
|
||||
<li><a href="#lang.declarations">SWAP</a></li>
|
||||
<li><a href="#lang.declarations">ERASE</a></li>
|
||||
<li><a href="#lang.conditionals">IF</a></li>
|
||||
<li><a href="#lang.conditionals">THEN</a></li>
|
||||
<li><a href="#ide.menu.edit">Delete</a></li>
|
||||
<li><a href="#ide.designer">Design Surface</a></li>
|
||||
<li><a href="#ide.menu.view">Design View</a></li>
|
||||
<li><a href="#ide.overview">Development Environment</a></li>
|
||||
<li><a href="#lang.declarations">DIM</a></li>
|
||||
<li><a href="#lang.loops">DO</a></li>
|
||||
<li><a href="#lang.forms">DoEvents</a></li>
|
||||
<li><a href="#lang.forms">DOEVENTS</a></li>
|
||||
<li><a href="#lang.datatypes">Double</a></li>
|
||||
<li><a href="#ctrl.picturebox">Drawing</a></li>
|
||||
<li><a href="#ctrl.dropdown">DropDown</a></li>
|
||||
<li><a href="#ide.overview">DVX BASIC</a></li>
|
||||
<li><a href="#ide.menu.edit">Edit Menu</a></li>
|
||||
<li><a href="#lang.conditionals">ELSE</a></li>
|
||||
<li><a href="#lang.conditionals">ELSEIF</a></li>
|
||||
<li><a href="#lang.conditionals">END IF</a></li>
|
||||
<li><a href="#lang.conditionals">SELECT CASE</a></li>
|
||||
<li><a href="#lang.conditionals">CASE</a></li>
|
||||
<li><a href="#lang.conditionals">CASE ELSE</a></li>
|
||||
<li><a href="#lang.conditionals">END SELECT</a></li>
|
||||
<li><a href="#lang.loops">FOR</a></li>
|
||||
<li><a href="#lang.loops">NEXT</a></li>
|
||||
<li><a href="#lang.loops">STEP</a></li>
|
||||
<li><a href="#lang.loops">DO</a></li>
|
||||
<li><a href="#lang.loops">LOOP</a></li>
|
||||
<li><a href="#lang.loops">WHILE</a></li>
|
||||
<li><a href="#lang.loops">WEND</a></li>
|
||||
<li><a href="#lang.loops">UNTIL</a></li>
|
||||
<li><a href="#lang.loops">EXIT FOR</a></li>
|
||||
<li><a href="#lang.loops">EXIT DO</a></li>
|
||||
<li><a href="#lang.procedures">SUB</a></li>
|
||||
<li><a href="#lang.procedures">END SUB</a></li>
|
||||
<li><a href="#lang.procedures">FUNCTION</a></li>
|
||||
<li><a href="#lang.procedures">END FUNCTION</a></li>
|
||||
<li><a href="#lang.procedures">DEF FN</a></li>
|
||||
<li><a href="#lang.procedures">BYVAL</a></li>
|
||||
<li><a href="#lang.procedures">EXIT SUB</a></li>
|
||||
<li><a href="#lang.procedures">EXIT FUNCTION</a></li>
|
||||
<li><a href="#lang.flow">EXIT</a></li>
|
||||
<li><a href="#lang.flow">CALL</a></li>
|
||||
<li><a href="#lang.flow">GOTO</a></li>
|
||||
<li><a href="#lang.flow">GOSUB</a></li>
|
||||
<li><a href="#lang.flow">RETURN</a></li>
|
||||
<li><a href="#lang.flow">ON GOTO</a></li>
|
||||
<li><a href="#lang.flow">ON GOSUB</a></li>
|
||||
<li><a href="#lang.io">PRINT</a></li>
|
||||
<li><a href="#lang.io">INPUT</a></li>
|
||||
<li><a href="#lang.io">DATA</a></li>
|
||||
<li><a href="#lang.io">READ</a></li>
|
||||
<li><a href="#lang.io">RESTORE</a></li>
|
||||
<li><a href="#lang.io">SPC</a></li>
|
||||
<li><a href="#lang.io">TAB</a></li>
|
||||
<li><a href="#lang.io">PRINT USING</a></li>
|
||||
<li><a href="#lang.misc">ON ERROR</a></li>
|
||||
<li><a href="#lang.misc">RESUME</a></li>
|
||||
<li><a href="#lang.misc">RESUME NEXT</a></li>
|
||||
<li><a href="#lang.misc">ERROR</a></li>
|
||||
<li><a href="#lang.misc">ERR</a></li>
|
||||
<li><a href="#lang.misc">SHELL</a></li>
|
||||
<li><a href="#lang.misc">SLEEP</a></li>
|
||||
<li><a href="#lang.misc">RANDOMIZE</a></li>
|
||||
<li><a href="#lang.misc">END</a></li>
|
||||
<li><a href="#lang.misc">RANDOMIZE TIMER</a></li>
|
||||
<li><a href="#lang.fileio">OPEN</a></li>
|
||||
<li><a href="#lang.fileio">CLOSE</a></li>
|
||||
<li><a href="#lang.fileio">PRINT #</a></li>
|
||||
<li><a href="#lang.fileio">INPUT #</a></li>
|
||||
<li><a href="#lang.fileio">LINE INPUT</a></li>
|
||||
<li><a href="#lang.fileio">WRITE #</a></li>
|
||||
<li><a href="#lang.fileio">GET</a></li>
|
||||
<li><a href="#lang.fileio">PUT</a></li>
|
||||
<li><a href="#lang.fileio">SEEK</a></li>
|
||||
<li><a href="#lang.procedures">END FUNCTION</a></li>
|
||||
<li><a href="#lang.conditionals">END IF</a></li>
|
||||
<li><a href="#lang.conditionals">END SELECT</a></li>
|
||||
<li><a href="#lang.procedures">END SUB</a></li>
|
||||
<li><a href="#lang.declarations">END TYPE</a></li>
|
||||
<li><a href="#lang.func.misc">ENVIRON$</a></li>
|
||||
<li><a href="#lang.func.fileio">EOF</a></li>
|
||||
<li><a href="#lang.operators">EQV</a></li>
|
||||
<li><a href="#lang.declarations">ERASE</a></li>
|
||||
<li><a href="#lang.misc">ERR</a></li>
|
||||
<li><a href="#lang.misc">ERROR</a></li>
|
||||
<li><a href="#lang.forms">Event Handlers</a></li>
|
||||
<li><a href="#ctrl.common.props">Events</a></li>
|
||||
<li><a href="#ide.menu.file">Exit</a></li>
|
||||
<li><a href="#lang.flow">EXIT</a></li>
|
||||
<li><a href="#lang.loops">EXIT DO</a></li>
|
||||
<li><a href="#lang.loops">EXIT FOR</a></li>
|
||||
<li><a href="#lang.procedures">EXIT FUNCTION</a></li>
|
||||
<li><a href="#lang.procedures">EXIT SUB</a></li>
|
||||
<li><a href="#lang.func.math">EXP</a></li>
|
||||
<li><a href="#ide.immediate">Expression Evaluation</a></li>
|
||||
<li><a href="#lang.constants">False</a></li>
|
||||
<li><a href="#lang.filesystem">File Attributes</a></li>
|
||||
<li><a href="#ide.menu.file">File Menu</a></li>
|
||||
<li><a href="#lang.filesystem">FILECOPY</a></li>
|
||||
<li><a href="#ide.menu.edit">Find</a></li>
|
||||
<li><a href="#ide.findreplace">Find</a></li>
|
||||
<li><a href="#ide.menu.edit">Find Next</a></li>
|
||||
<li><a href="#ide.findreplace">Find Next</a></li>
|
||||
<li><a href="#ctrl.textarea">FindNext</a></li>
|
||||
<li><a href="#lang.func.math">FIX</a></li>
|
||||
<li><a href="#ctrl.wrapbox">Flow Layout</a></li>
|
||||
<li><a href="#lang.loops">FOR</a></li>
|
||||
<li><a href="#lang.fileio">FOR APPEND</a></li>
|
||||
<li><a href="#lang.fileio">FOR BINARY</a></li>
|
||||
<li><a href="#lang.fileio">FOR INPUT</a></li>
|
||||
<li><a href="#lang.fileio">FOR OUTPUT</a></li>
|
||||
<li><a href="#lang.fileio">FOR APPEND</a></li>
|
||||
<li><a href="#lang.fileio">FOR RANDOM</a></li>
|
||||
<li><a href="#lang.fileio">FOR BINARY</a></li>
|
||||
<li><a href="#lang.filesystem">KILL</a></li>
|
||||
<li><a href="#lang.filesystem">NAME</a></li>
|
||||
<li><a href="#lang.filesystem">FILECOPY</a></li>
|
||||
<li><a href="#lang.filesystem">MKDIR</a></li>
|
||||
<li><a href="#lang.filesystem">RMDIR</a></li>
|
||||
<li><a href="#lang.filesystem">CHDIR</a></li>
|
||||
<li><a href="#lang.filesystem">CHDRIVE</a></li>
|
||||
<li><a href="#lang.filesystem">GETATTR</a></li>
|
||||
<li><a href="#lang.filesystem">SETATTR</a></li>
|
||||
<li><a href="#lang.filesystem">File Attributes</a></li>
|
||||
<li><a href="#lang.func.string">ASC</a></li>
|
||||
<li><a href="#lang.func.string">CHR$</a></li>
|
||||
<li><a href="#ctrl.form">Form</a></li>
|
||||
<li><a href="#ide.designer">Form Designer</a></li>
|
||||
<li><a href="#ctrl.frm">Form File</a></li>
|
||||
<li><a href="#lang.func.string">FORMAT$</a></li>
|
||||
<li><a href="#ctrl.frame">Frame</a></li>
|
||||
<li><a href="#lang.func.fileio">FREEFILE</a></li>
|
||||
<li><a href="#ctrl.frm">FRM</a></li>
|
||||
<li><a href="#lang.procedures">FUNCTION</a></li>
|
||||
<li><a href="#ide.editor">Function Dropdown</a></li>
|
||||
<li><a href="#lang.fileio">GET</a></li>
|
||||
<li><a href="#lang.filesystem">GETATTR</a></li>
|
||||
<li><a href="#lang.flow">GOSUB</a></li>
|
||||
<li><a href="#lang.flow">GOTO</a></li>
|
||||
<li><a href="#ctrl.textarea">GoToLine</a></li>
|
||||
<li><a href="#ide.designer">Grab Handles</a></li>
|
||||
<li><a href="#ide.designer">Grid Snapping</a></li>
|
||||
<li><a href="#ctrl.hbox">HBox</a></li>
|
||||
<li><a href="#ide.menu.help">Help Menu</a></li>
|
||||
<li><a href="#lang.runtime">help.bas</a></li>
|
||||
<li><a href="#lang.func.string">HEX$</a></li>
|
||||
<li><a href="#lang.forms">Hide</a></li>
|
||||
<li><a href="#ctrl.form">Hide</a></li>
|
||||
<li><a href="#ctrl.hbox">Horizontal Layout</a></li>
|
||||
<li><a href="#ide.shortcuts">Hotkeys</a></li>
|
||||
<li><a href="#ctrl.hscrollbar">HScrollBar</a></li>
|
||||
<li><a href="#ide.overview">IDE</a></li>
|
||||
<li><a href="#lang.conditionals">IF</a></li>
|
||||
<li><a href="#ctrl.image">Image</a></li>
|
||||
<li><a href="#ctrl.imagebutton">ImageButton</a></li>
|
||||
<li><a href="#ide.immediate">Immediate Window</a></li>
|
||||
<li><a href="#lang.operators">IMP</a></li>
|
||||
<li><a href="#lang.runtime">Include Libraries</a></li>
|
||||
<li><a href="#ctrl.arrays">Index Property</a></li>
|
||||
<li><a href="#lang.ini">INI</a></li>
|
||||
<li><a href="#lang.ini">IniRead</a></li>
|
||||
<li><a href="#lang.ini">IniWrite</a></li>
|
||||
<li><a href="#lang.io">INPUT</a></li>
|
||||
<li><a href="#lang.fileio">INPUT #</a></li>
|
||||
<li><a href="#lang.func.fileio">INPUT$</a></li>
|
||||
<li><a href="#lang.forms">InputBox$</a></li>
|
||||
<li><a href="#lang.forms">INPUTBOX$</a></li>
|
||||
<li><a href="#lang.func.string">INSTR</a></li>
|
||||
<li><a href="#lang.func.math">INT</a></li>
|
||||
<li><a href="#lang.datatypes">Integer</a></li>
|
||||
<li><a href="#ctrl.timer">Interval</a></li>
|
||||
<li><a href="#ide.shortcuts">Keyboard Shortcuts</a></li>
|
||||
<li><a href="#lang.filesystem">KILL</a></li>
|
||||
<li><a href="#ctrl.label">Label</a></li>
|
||||
<li><a href="#lang.func.fileio">LBOUND</a></li>
|
||||
<li><a href="#lang.func.string">LCASE$</a></li>
|
||||
<li><a href="#lang.func.string">LEFT$</a></li>
|
||||
<li><a href="#lang.func.string">LEN</a></li>
|
||||
<li><a href="#lang.func.string">LTRIM$</a></li>
|
||||
<li><a href="#lang.func.string">MID$</a></li>
|
||||
<li><a href="#lang.func.string">OCT$</a></li>
|
||||
<li><a href="#lang.func.string">RIGHT$</a></li>
|
||||
<li><a href="#lang.func.string">RTRIM$</a></li>
|
||||
<li><a href="#lang.func.string">SPACE$</a></li>
|
||||
<li><a href="#lang.func.string">STR$</a></li>
|
||||
<li><a href="#lang.func.string">STRING$</a></li>
|
||||
<li><a href="#lang.func.string">TRIM$</a></li>
|
||||
<li><a href="#lang.func.string">UCASE$</a></li>
|
||||
<li><a href="#lang.func.string">VAL</a></li>
|
||||
<li><a href="#lang.func.math">ABS</a></li>
|
||||
<li><a href="#lang.func.math">ATN</a></li>
|
||||
<li><a href="#lang.func.math">COS</a></li>
|
||||
<li><a href="#lang.func.math">EXP</a></li>
|
||||
<li><a href="#lang.func.math">FIX</a></li>
|
||||
<li><a href="#lang.func.math">INT</a></li>
|
||||
<li><a href="#lang.func.math">LOG</a></li>
|
||||
<li><a href="#lang.func.math">RND</a></li>
|
||||
<li><a href="#lang.func.math">SGN</a></li>
|
||||
<li><a href="#lang.func.math">SIN</a></li>
|
||||
<li><a href="#lang.func.math">SQR</a></li>
|
||||
<li><a href="#lang.func.math">TAN</a></li>
|
||||
<li><a href="#lang.func.math">TIMER</a></li>
|
||||
<li><a href="#lang.func.conversion">CBOOL</a></li>
|
||||
<li><a href="#lang.func.conversion">CDBL</a></li>
|
||||
<li><a href="#lang.func.conversion">CINT</a></li>
|
||||
<li><a href="#lang.func.conversion">CLNG</a></li>
|
||||
<li><a href="#lang.func.conversion">CSNG</a></li>
|
||||
<li><a href="#lang.func.conversion">CSTR</a></li>
|
||||
<li><a href="#lang.func.fileio">EOF</a></li>
|
||||
<li><a href="#lang.func.fileio">FREEFILE</a></li>
|
||||
<li><a href="#lang.func.fileio">INPUT$</a></li>
|
||||
<li><a href="#lang.func.fileio">LOC</a></li>
|
||||
<li><a href="#lang.func.fileio">LOF</a></li>
|
||||
<li><a href="#lang.func.fileio">LBOUND</a></li>
|
||||
<li><a href="#lang.func.fileio">UBOUND</a></li>
|
||||
<li><a href="#lang.func.misc">DATE$</a></li>
|
||||
<li><a href="#lang.func.misc">TIME$</a></li>
|
||||
<li><a href="#lang.func.misc">ENVIRON$</a></li>
|
||||
<li><a href="#lang.declarations">LET</a></li>
|
||||
<li><a href="#ctrl.line">Line</a></li>
|
||||
<li><a href="#lang.fileio">LINE INPUT</a></li>
|
||||
<li><a href="#ide.editor">Line Numbers</a></li>
|
||||
<li><a href="#ctrl.textarea">LineNumbers</a></li>
|
||||
<li><a href="#ctrl.listbox">ListBox</a></li>
|
||||
<li><a href="#ctrl.listbox">ListCount</a></li>
|
||||
<li><a href="#ctrl.listbox">ListIndex</a></li>
|
||||
<li><a href="#ctrl.listview">ListView</a></li>
|
||||
<li><a href="#lang.forms">LOAD</a></li>
|
||||
<li><a href="#lang.forms">UNLOAD</a></li>
|
||||
<li><a href="#lang.forms">Show</a></li>
|
||||
<li><a href="#lang.forms">Hide</a></li>
|
||||
<li><a href="#lang.forms">Me</a></li>
|
||||
<li><a href="#lang.forms">Nothing</a></li>
|
||||
<li><a href="#lang.forms">DoEvents</a></li>
|
||||
<li><a href="#lang.forms">DOEVENTS</a></li>
|
||||
<li><a href="#lang.forms">MsgBox</a></li>
|
||||
<li><a href="#lang.forms">MSGBOX</a></li>
|
||||
<li><a href="#lang.forms">InputBox$</a></li>
|
||||
<li><a href="#lang.forms">INPUTBOX$</a></li>
|
||||
<li><a href="#lang.forms">CreateForm</a></li>
|
||||
<li><a href="#lang.forms">CreateControl</a></li>
|
||||
<li><a href="#lang.forms">SetEvent</a></li>
|
||||
<li><a href="#lang.forms">RemoveControl</a></li>
|
||||
<li><a href="#lang.forms">With</a></li>
|
||||
<li><a href="#lang.forms">vbModal</a></li>
|
||||
<li><a href="#lang.forms">Control Arrays</a></li>
|
||||
<li><a href="#lang.forms">Event Handlers</a></li>
|
||||
<li><a href="#lang.sql">SQLOpen</a></li>
|
||||
<li><a href="#lang.sql">SQLClose</a></li>
|
||||
<li><a href="#lang.sql">SQLExec</a></li>
|
||||
<li><a href="#lang.sql">SQLAffected</a></li>
|
||||
<li><a href="#lang.sql">SQLQuery</a></li>
|
||||
<li><a href="#lang.sql">SQLNext</a></li>
|
||||
<li><a href="#lang.sql">SQLEof</a></li>
|
||||
<li><a href="#lang.sql">SQLField$</a></li>
|
||||
<li><a href="#lang.sql">SQLFieldInt</a></li>
|
||||
<li><a href="#lang.sql">SQLFieldDbl</a></li>
|
||||
<li><a href="#lang.sql">SQLFieldCount</a></li>
|
||||
<li><a href="#lang.sql">SQLFreeResult</a></li>
|
||||
<li><a href="#lang.sql">SQLError$</a></li>
|
||||
<li><a href="#lang.sql">SQLite</a></li>
|
||||
<li><a href="#lang.app">App</a></li>
|
||||
<li><a href="#lang.app">App.Path</a></li>
|
||||
<li><a href="#lang.app">App.Config</a></li>
|
||||
<li><a href="#lang.app">App.Data</a></li>
|
||||
<li><a href="#lang.ini">IniRead</a></li>
|
||||
<li><a href="#lang.ini">IniWrite</a></li>
|
||||
<li><a href="#lang.ini">INI</a></li>
|
||||
<li><a href="#lang.constants">vbOKOnly</a></li>
|
||||
<li><a href="#lang.constants">vbOKCancel</a></li>
|
||||
<li><a href="#lang.constants">vbYesNo</a></li>
|
||||
<li><a href="#lang.constants">vbYesNoCancel</a></li>
|
||||
<li><a href="#lang.constants">vbRetryCancel</a></li>
|
||||
<li><a href="#lang.constants">vbInformation</a></li>
|
||||
<li><a href="#lang.constants">vbExclamation</a></li>
|
||||
<li><a href="#lang.constants">vbCritical</a></li>
|
||||
<li><a href="#lang.constants">vbQuestion</a></li>
|
||||
<li><a href="#lang.constants">vbOK</a></li>
|
||||
<li><a href="#lang.constants">vbCancel</a></li>
|
||||
<li><a href="#lang.constants">vbYes</a></li>
|
||||
<li><a href="#lang.constants">vbNo</a></li>
|
||||
<li><a href="#lang.constants">vbRetry</a></li>
|
||||
<li><a href="#lang.constants">vbNormal</a></li>
|
||||
<li><a href="#lang.constants">vbReadOnly</a></li>
|
||||
<li><a href="#lang.constants">vbHidden</a></li>
|
||||
<li><a href="#lang.constants">vbSystem</a></li>
|
||||
<li><a href="#lang.constants">vbDirectory</a></li>
|
||||
<li><a href="#lang.constants">vbArchive</a></li>
|
||||
<li><a href="#lang.constants">True</a></li>
|
||||
<li><a href="#lang.constants">False</a></li>
|
||||
<li><a href="#lang.constants">Predefined Constants</a></li>
|
||||
<li><a href="#lang.runtime">Include Libraries</a></li>
|
||||
<li><a href="#lang.runtime">commdlg.bas</a></li>
|
||||
<li><a href="#lang.runtime">sql.bas</a></li>
|
||||
<li><a href="#lang.runtime">comm.bas</a></li>
|
||||
<li><a href="#lang.runtime">resource.bas</a></li>
|
||||
<li><a href="#lang.runtime">help.bas</a></li>
|
||||
<li><a href="#lang.runtime">DECLARE LIBRARY</a></li>
|
||||
<li><a href="#ctrl.common.props">Common Properties</a></li>
|
||||
<li><a href="#ctrl.common.props">Common Events</a></li>
|
||||
<li><a href="#ctrl.common.props">Common Methods</a></li>
|
||||
<li><a href="#ctrl.common.props">Properties</a></li>
|
||||
<li><a href="#ctrl.common.props">Events</a></li>
|
||||
<li><a href="#ctrl.common.props">Methods</a></li>
|
||||
<li><a href="#ctrl.databinding">Data Binding</a></li>
|
||||
<li><a href="#ctrl.databinding">DataSource</a></li>
|
||||
<li><a href="#ctrl.databinding">DataField</a></li>
|
||||
<li><a href="#ctrl.form">Load</a></li>
|
||||
<li><a href="#lang.func.fileio">LOC</a></li>
|
||||
<li><a href="#ide.debug.locals">Locals Window</a></li>
|
||||
<li><a href="#lang.func.fileio">LOF</a></li>
|
||||
<li><a href="#lang.func.math">LOG</a></li>
|
||||
<li><a href="#lang.datatypes">Long</a></li>
|
||||
<li><a href="#lang.loops">LOOP</a></li>
|
||||
<li><a href="#lang.func.string">LTRIM$</a></li>
|
||||
<li><a href="#ctrl.databinding">Master-Detail</a></li>
|
||||
<li><a href="#lang.forms">Me</a></li>
|
||||
<li><a href="#ctrl.menus">Menu</a></li>
|
||||
<li><a href="#ctrl.menus">Menu Bar</a></li>
|
||||
<li><a href="#ctrl.menus">Submenu</a></li>
|
||||
<li><a href="#ctrl.menus">Separator</a></li>
|
||||
<li><a href="#ctrl.arrays">Control Arrays</a></li>
|
||||
<li><a href="#ctrl.arrays">Index Property</a></li>
|
||||
<li><a href="#ctrl.frm">FRM</a></li>
|
||||
<li><a href="#ctrl.frm">.frm</a></li>
|
||||
<li><a href="#ctrl.frm">Form File</a></li>
|
||||
<li><a href="#ctrl.form">Form</a></li>
|
||||
<li><a href="#ctrl.form">Window</a></li>
|
||||
<li><a href="#ctrl.form">Caption</a></li>
|
||||
<li><a href="#ctrl.form">AutoSize</a></li>
|
||||
<li><a href="#ctrl.form">Resizable</a></li>
|
||||
<li><a href="#ctrl.form">Load</a></li>
|
||||
<li><a href="#ctrl.form">Unload</a></li>
|
||||
<li><a href="#ctrl.form">Show</a></li>
|
||||
<li><a href="#ctrl.form">Hide</a></li>
|
||||
<li><a href="#ctrl.terminal">Terminal</a></li>
|
||||
<li><a href="#ctrl.terminal">ANSI Terminal</a></li>
|
||||
<li><a href="#ctrl.terminal">VT100</a></li>
|
||||
<li><a href="#ctrl.frame">Frame</a></li>
|
||||
<li><a href="#ctrl.frame">Container</a></li>
|
||||
<li><a href="#ctrl.hbox">HBox</a></li>
|
||||
<li><a href="#ctrl.hbox">Horizontal Layout</a></li>
|
||||
<li><a href="#ctrl.vbox">VBox</a></li>
|
||||
<li><a href="#ctrl.vbox">Vertical Layout</a></li>
|
||||
<li><a href="#ctrl.button">CommandButton</a></li>
|
||||
<li><a href="#ctrl.button">Button</a></li>
|
||||
<li><a href="#ctrl.button">Click</a></li>
|
||||
<li><a href="#ctrl.picturebox">PictureBox</a></li>
|
||||
<li><a href="#ctrl.picturebox">Canvas</a></li>
|
||||
<li><a href="#ctrl.picturebox">Drawing</a></li>
|
||||
<li><a href="#ctrl.picturebox">SetPenColor</a></li>
|
||||
<li><a href="#ctrl.picturebox">SetPenSize</a></li>
|
||||
<li><a href="#ctrl.checkbox">CheckBox</a></li>
|
||||
<li><a href="#ctrl.checkbox">Value</a></li>
|
||||
<li><a href="#ctrl.combobox">ComboBox</a></li>
|
||||
<li><a href="#ctrl.data">Data</a></li>
|
||||
<li><a href="#ctrl.data">Database</a></li>
|
||||
<li><a href="#ctrl.data">SQLite</a></li>
|
||||
<li><a href="#ctrl.data">DatabaseName</a></li>
|
||||
<li><a href="#ctrl.data">RecordSource</a></li>
|
||||
<li><a href="#ide.menu.view">Menu Editor</a></li>
|
||||
<li><a href="#ctrl.common.props">Methods</a></li>
|
||||
<li><a href="#lang.func.string">MID$</a></li>
|
||||
<li><a href="#lang.filesystem">MKDIR</a></li>
|
||||
<li><a href="#lang.operators">MOD</a></li>
|
||||
<li><a href="#ctrl.data">MoveFirst</a></li>
|
||||
<li><a href="#ctrl.data">MoveNext</a></li>
|
||||
<li><a href="#ctrl.data">AddNew</a></li>
|
||||
<li><a href="#ctrl.data">Reposition</a></li>
|
||||
<li><a href="#ctrl.data">Validate</a></li>
|
||||
<li><a href="#ctrl.dbgrid">DBGrid</a></li>
|
||||
<li><a href="#ctrl.dbgrid">Data-Bound Grid</a></li>
|
||||
<li><a href="#ctrl.dropdown">DropDown</a></li>
|
||||
<li><a href="#ctrl.imagebutton">ImageButton</a></li>
|
||||
<li><a href="#ctrl.image">Image</a></li>
|
||||
<li><a href="#ctrl.image">Picture</a></li>
|
||||
<li><a href="#ctrl.image">BMP</a></li>
|
||||
<li><a href="#ctrl.label">Label</a></li>
|
||||
<li><a href="#ctrl.label">Caption</a></li>
|
||||
<li><a href="#ctrl.label">Alignment</a></li>
|
||||
<li><a href="#ctrl.listbox">ListBox</a></li>
|
||||
<li><a href="#ctrl.listbox">ListIndex</a></li>
|
||||
<li><a href="#ctrl.listbox">ListCount</a></li>
|
||||
<li><a href="#ctrl.listbox">AddItem</a></li>
|
||||
<li><a href="#ctrl.listbox">RemoveItem</a></li>
|
||||
<li><a href="#ctrl.listview">ListView</a></li>
|
||||
<li><a href="#lang.forms">MsgBox</a></li>
|
||||
<li><a href="#lang.forms">MSGBOX</a></li>
|
||||
<li><a href="#ctrl.listview">Multi-Column List</a></li>
|
||||
<li><a href="#ctrl.listview">SetColumns</a></li>
|
||||
<li><a href="#ctrl.listview">SetSort</a></li>
|
||||
<li><a href="#ctrl.progressbar">ProgressBar</a></li>
|
||||
<li><a href="#lang.filesystem">NAME</a></li>
|
||||
<li><a href="#ide.menu.file">New Project</a></li>
|
||||
<li><a href="#lang.loops">NEXT</a></li>
|
||||
<li><a href="#lang.operators">NOT</a></li>
|
||||
<li><a href="#lang.forms">Nothing</a></li>
|
||||
<li><a href="#ide.editor">Object Dropdown</a></li>
|
||||
<li><a href="#lang.func.string">OCT$</a></li>
|
||||
<li><a href="#lang.misc">ON ERROR</a></li>
|
||||
<li><a href="#lang.flow">ON GOSUB</a></li>
|
||||
<li><a href="#lang.flow">ON GOTO</a></li>
|
||||
<li><a href="#lang.fileio">OPEN</a></li>
|
||||
<li><a href="#ide.menu.file">Open Project</a></li>
|
||||
<li><a href="#lang.operators">Operators</a></li>
|
||||
<li><a href="#lang.declarations">OPTION</a></li>
|
||||
<li><a href="#lang.declarations">OPTION BASE</a></li>
|
||||
<li><a href="#lang.declarations">OPTION COMPARE</a></li>
|
||||
<li><a href="#ide.preferences">Option Explicit</a></li>
|
||||
<li><a href="#lang.declarations">OPTION EXPLICIT</a></li>
|
||||
<li><a href="#ctrl.optionbutton">OptionButton</a></li>
|
||||
<li><a href="#lang.operators">OR</a></li>
|
||||
<li><a href="#ide.output">Output Window</a></li>
|
||||
<li><a href="#ide.menu.edit">Paste</a></li>
|
||||
<li><a href="#ctrl.image">Picture</a></li>
|
||||
<li><a href="#ctrl.picturebox">PictureBox</a></li>
|
||||
<li><a href="#lang.operators">Precedence</a></li>
|
||||
<li><a href="#lang.constants">Predefined Constants</a></li>
|
||||
<li><a href="#ide.menu.tools">Preferences</a></li>
|
||||
<li><a href="#ide.preferences">Preferences</a></li>
|
||||
<li><a href="#lang.io">PRINT</a></li>
|
||||
<li><a href="#lang.fileio">PRINT #</a></li>
|
||||
<li><a href="#ide.output">PRINT Output</a></li>
|
||||
<li><a href="#lang.io">PRINT USING</a></li>
|
||||
<li><a href="#ctrl.progressbar">ProgressBar</a></li>
|
||||
<li><a href="#ide.project">Project Explorer</a></li>
|
||||
<li><a href="#ide.project">Project Files</a></li>
|
||||
<li><a href="#ide.menu.file">Project Properties</a></li>
|
||||
<li><a href="#ide.project">Project System</a></li>
|
||||
<li><a href="#ctrl.common.props">Properties</a></li>
|
||||
<li><a href="#ide.properties">Properties Panel</a></li>
|
||||
<li><a href="#ide.properties">Property List</a></li>
|
||||
<li><a href="#lang.fileio">PUT</a></li>
|
||||
<li><a href="#ctrl.optionbutton">Radio Button</a></li>
|
||||
<li><a href="#ctrl.optionbutton">SetSelected</a></li>
|
||||
<li><a href="#ctrl.scrollpane">ScrollPane</a></li>
|
||||
<li><a href="#lang.misc">RANDOMIZE</a></li>
|
||||
<li><a href="#lang.misc">RANDOMIZE TIMER</a></li>
|
||||
<li><a href="#lang.io">READ</a></li>
|
||||
<li><a href="#ctrl.spinbutton">RealMode</a></li>
|
||||
<li><a href="#ctrl.data">RecordSource</a></li>
|
||||
<li><a href="#lang.declarations">REDIM</a></li>
|
||||
<li><a href="#lang.statements">REM</a></li>
|
||||
<li><a href="#ide.menu.file">Remove File</a></li>
|
||||
<li><a href="#lang.forms">RemoveControl</a></li>
|
||||
<li><a href="#ctrl.listbox">RemoveItem</a></li>
|
||||
<li><a href="#ide.immediate">REPL</a></li>
|
||||
<li><a href="#ide.menu.edit">Replace</a></li>
|
||||
<li><a href="#ide.findreplace">Replace</a></li>
|
||||
<li><a href="#ctrl.textarea">ReplaceAll</a></li>
|
||||
<li><a href="#ctrl.data">Reposition</a></li>
|
||||
<li><a href="#ctrl.form">Resizable</a></li>
|
||||
<li><a href="#lang.runtime">resource.bas</a></li>
|
||||
<li><a href="#lang.io">RESTORE</a></li>
|
||||
<li><a href="#lang.misc">RESUME</a></li>
|
||||
<li><a href="#lang.misc">RESUME NEXT</a></li>
|
||||
<li><a href="#lang.flow">RETURN</a></li>
|
||||
<li><a href="#lang.func.string">RIGHT$</a></li>
|
||||
<li><a href="#lang.filesystem">RMDIR</a></li>
|
||||
<li><a href="#lang.func.math">RND</a></li>
|
||||
<li><a href="#lang.func.string">RTRIM$</a></li>
|
||||
<li><a href="#ide.menu.run">Run</a></li>
|
||||
<li><a href="#ide.menu.run">Run Menu</a></li>
|
||||
<li><a href="#ide.menu.run">Run to Cursor</a></li>
|
||||
<li><a href="#ide.menu.run">Run Without Recompile</a></li>
|
||||
<li><a href="#ide.output">Runtime Errors</a></li>
|
||||
<li><a href="#ide.menu.file">Save All</a></li>
|
||||
<li><a href="#ide.menu.file">Save File</a></li>
|
||||
<li><a href="#ide.menu.run">Save on Run</a></li>
|
||||
<li><a href="#ide.menu.file">Save Project</a></li>
|
||||
<li><a href="#ctrl.scrollpane">Scrollable Container</a></li>
|
||||
<li><a href="#ctrl.line">Line</a></li>
|
||||
<li><a href="#ctrl.scrollpane">ScrollPane</a></li>
|
||||
<li><a href="#ide.findreplace">Search</a></li>
|
||||
<li><a href="#lang.fileio">SEEK</a></li>
|
||||
<li><a href="#ide.menu.edit">Select All</a></li>
|
||||
<li><a href="#lang.conditionals">SELECT CASE</a></li>
|
||||
<li><a href="#ctrl.menus">Separator</a></li>
|
||||
<li><a href="#ctrl.line">Separator</a></li>
|
||||
<li><a href="#ctrl.hscrollbar">HScrollBar</a></li>
|
||||
<li><a href="#ctrl.tabstrip">SetActive</a></li>
|
||||
<li><a href="#lang.filesystem">SETATTR</a></li>
|
||||
<li><a href="#ctrl.listview">SetColumns</a></li>
|
||||
<li><a href="#lang.forms">SetEvent</a></li>
|
||||
<li><a href="#ctrl.picturebox">SetPenColor</a></li>
|
||||
<li><a href="#ctrl.picturebox">SetPenSize</a></li>
|
||||
<li><a href="#ctrl.spinbutton">SetRange</a></li>
|
||||
<li><a href="#ctrl.optionbutton">SetSelected</a></li>
|
||||
<li><a href="#ctrl.listview">SetSort</a></li>
|
||||
<li><a href="#ctrl.spinbutton">SetStep</a></li>
|
||||
<li><a href="#ide.preferences">Settings</a></li>
|
||||
<li><a href="#lang.func.math">SGN</a></li>
|
||||
<li><a href="#lang.declarations">SHARED</a></li>
|
||||
<li><a href="#lang.misc">SHELL</a></li>
|
||||
<li><a href="#lang.forms">Show</a></li>
|
||||
<li><a href="#ctrl.form">Show</a></li>
|
||||
<li><a href="#lang.func.math">SIN</a></li>
|
||||
<li><a href="#lang.datatypes">Single</a></li>
|
||||
<li><a href="#lang.misc">SLEEP</a></li>
|
||||
<li><a href="#ctrl.hscrollbar">Slider</a></li>
|
||||
<li><a href="#ide.project">Source Map</a></li>
|
||||
<li><a href="#lang.func.string">SPACE$</a></li>
|
||||
<li><a href="#ctrl.spacer">Spacer</a></li>
|
||||
<li><a href="#lang.io">SPC</a></li>
|
||||
<li><a href="#ctrl.spinbutton">SpinButton</a></li>
|
||||
<li><a href="#ctrl.spinbutton">Spinner</a></li>
|
||||
<li><a href="#ctrl.spinbutton">SetRange</a></li>
|
||||
<li><a href="#ctrl.spinbutton">SetStep</a></li>
|
||||
<li><a href="#ctrl.spinbutton">RealMode</a></li>
|
||||
<li><a href="#ctrl.spinbutton">Decimals</a></li>
|
||||
<li><a href="#ctrl.splitter">Splitter</a></li>
|
||||
<li><a href="#ctrl.splitter">Split Pane</a></li>
|
||||
<li><a href="#ctrl.statusbar">StatusBar</a></li>
|
||||
<li><a href="#ctrl.tabstrip">TabStrip</a></li>
|
||||
<li><a href="#ctrl.tabstrip">TabControl</a></li>
|
||||
<li><a href="#ctrl.tabstrip">SetActive</a></li>
|
||||
<li><a href="#ctrl.textbox">TextBox</a></li>
|
||||
<li><a href="#ctrl.textbox">Text</a></li>
|
||||
<li><a href="#ctrl.textbox">DataSource</a></li>
|
||||
<li><a href="#ctrl.textbox">DataField</a></li>
|
||||
<li><a href="#ctrl.textarea">TextArea</a></li>
|
||||
<li><a href="#ctrl.textarea">FindNext</a></li>
|
||||
<li><a href="#ctrl.textarea">ReplaceAll</a></li>
|
||||
<li><a href="#ctrl.textarea">GoToLine</a></li>
|
||||
<li><a href="#ctrl.textarea">CursorLine</a></li>
|
||||
<li><a href="#ctrl.textarea">LineNumbers</a></li>
|
||||
<li><a href="#ctrl.textarea">AutoIndent</a></li>
|
||||
<li><a href="#ctrl.timer">Timer</a></li>
|
||||
<li><a href="#ctrl.timer">Interval</a></li>
|
||||
<li><a href="#ctrl.splitter">Splitter</a></li>
|
||||
<li><a href="#lang.runtime">sql.bas</a></li>
|
||||
<li><a href="#lang.sql">SQLAffected</a></li>
|
||||
<li><a href="#lang.sql">SQLClose</a></li>
|
||||
<li><a href="#lang.sql">SQLEof</a></li>
|
||||
<li><a href="#lang.sql">SQLError$</a></li>
|
||||
<li><a href="#lang.sql">SQLExec</a></li>
|
||||
<li><a href="#lang.sql">SQLField$</a></li>
|
||||
<li><a href="#lang.sql">SQLFieldCount</a></li>
|
||||
<li><a href="#lang.sql">SQLFieldDbl</a></li>
|
||||
<li><a href="#lang.sql">SQLFieldInt</a></li>
|
||||
<li><a href="#lang.sql">SQLFreeResult</a></li>
|
||||
<li><a href="#lang.sql">SQLite</a></li>
|
||||
<li><a href="#ctrl.data">SQLite</a></li>
|
||||
<li><a href="#lang.sql">SQLNext</a></li>
|
||||
<li><a href="#lang.sql">SQLOpen</a></li>
|
||||
<li><a href="#lang.sql">SQLQuery</a></li>
|
||||
<li><a href="#lang.func.math">SQR</a></li>
|
||||
<li><a href="#ide.debug.callstack">Stack Trace</a></li>
|
||||
<li><a href="#ctrl.timer">Start</a></li>
|
||||
<li><a href="#lang.statements">Statements</a></li>
|
||||
<li><a href="#lang.declarations">STATIC</a></li>
|
||||
<li><a href="#ide.menu.view">Status Bar Toggle</a></li>
|
||||
<li><a href="#ctrl.statusbar">StatusBar</a></li>
|
||||
<li><a href="#lang.loops">STEP</a></li>
|
||||
<li><a href="#ide.menu.run">Step Into</a></li>
|
||||
<li><a href="#ide.menu.run">Step Out</a></li>
|
||||
<li><a href="#ide.menu.run">Step Over</a></li>
|
||||
<li><a href="#ide.debugger">Stepping</a></li>
|
||||
<li><a href="#ide.menu.run">Stop</a></li>
|
||||
<li><a href="#ctrl.timer">Stop</a></li>
|
||||
<li><a href="#lang.func.string">STR$</a></li>
|
||||
<li><a href="#lang.datatypes">String</a></li>
|
||||
<li><a href="#lang.func.string">STRING$</a></li>
|
||||
<li><a href="#lang.procedures">SUB</a></li>
|
||||
<li><a href="#ctrl.menus">Submenu</a></li>
|
||||
<li><a href="#lang.declarations">SWAP</a></li>
|
||||
<li><a href="#ide.editor">Syntax Highlighting</a></li>
|
||||
<li><a href="#lang.io">TAB</a></li>
|
||||
<li><a href="#ide.preferences">Tab Width</a></li>
|
||||
<li><a href="#ctrl.tabstrip">TabControl</a></li>
|
||||
<li><a href="#ctrl.tabstrip">TabStrip</a></li>
|
||||
<li><a href="#lang.func.math">TAN</a></li>
|
||||
<li><a href="#ctrl.terminal">Terminal</a></li>
|
||||
<li><a href="#ctrl.textbox">Text</a></li>
|
||||
<li><a href="#ctrl.textarea">TextArea</a></li>
|
||||
<li><a href="#ctrl.textbox">TextBox</a></li>
|
||||
<li><a href="#lang.conditionals">THEN</a></li>
|
||||
<li><a href="#lang.func.misc">TIME$</a></li>
|
||||
<li><a href="#lang.func.math">TIMER</a></li>
|
||||
<li><a href="#ctrl.timer">Timer</a></li>
|
||||
<li><a href="#ide.menu.run">Toggle Breakpoint</a></li>
|
||||
<li><a href="#ide.toolbar">Toolbar</a></li>
|
||||
<li><a href="#ctrl.toolbar">Toolbar</a></li>
|
||||
<li><a href="#ide.menu.view">Toolbar Toggle</a></li>
|
||||
<li><a href="#ide.toolbox">Toolbox</a></li>
|
||||
<li><a href="#ide.menu.tools">Tools Menu</a></li>
|
||||
<li><a href="#ctrl.treeview">TreeView</a></li>
|
||||
<li><a href="#ctrl.treeview">AddItem</a></li>
|
||||
<li><a href="#ctrl.treeview">AddChildItem</a></li>
|
||||
<li><a href="#lang.func.string">TRIM$</a></li>
|
||||
<li><a href="#lang.constants">True</a></li>
|
||||
<li><a href="#lang.declarations">TYPE</a></li>
|
||||
<li><a href="#lang.func.fileio">UBOUND</a></li>
|
||||
<li><a href="#lang.func.string">UCASE$</a></li>
|
||||
<li><a href="#lang.forms">UNLOAD</a></li>
|
||||
<li><a href="#ctrl.form">Unload</a></li>
|
||||
<li><a href="#lang.loops">UNTIL</a></li>
|
||||
<li><a href="#lang.func.string">VAL</a></li>
|
||||
<li><a href="#ctrl.data">Validate</a></li>
|
||||
<li><a href="#ctrl.checkbox">Value</a></li>
|
||||
<li><a href="#ide.immediate">Variable Assignment</a></li>
|
||||
<li><a href="#ide.debug.locals">Variable Inspection</a></li>
|
||||
<li><a href="#lang.constants">vbArchive</a></li>
|
||||
<li><a href="#lang.constants">vbCancel</a></li>
|
||||
<li><a href="#lang.constants">vbCritical</a></li>
|
||||
<li><a href="#lang.constants">vbDirectory</a></li>
|
||||
<li><a href="#lang.constants">vbExclamation</a></li>
|
||||
<li><a href="#lang.constants">vbHidden</a></li>
|
||||
<li><a href="#lang.constants">vbInformation</a></li>
|
||||
<li><a href="#lang.forms">vbModal</a></li>
|
||||
<li><a href="#lang.constants">vbNo</a></li>
|
||||
<li><a href="#lang.constants">vbNormal</a></li>
|
||||
<li><a href="#lang.constants">vbOK</a></li>
|
||||
<li><a href="#lang.constants">vbOKCancel</a></li>
|
||||
<li><a href="#lang.constants">vbOKOnly</a></li>
|
||||
<li><a href="#ctrl.vbox">VBox</a></li>
|
||||
<li><a href="#lang.constants">vbQuestion</a></li>
|
||||
<li><a href="#lang.constants">vbReadOnly</a></li>
|
||||
<li><a href="#lang.constants">vbRetry</a></li>
|
||||
<li><a href="#lang.constants">vbRetryCancel</a></li>
|
||||
<li><a href="#lang.constants">vbSystem</a></li>
|
||||
<li><a href="#lang.constants">vbYes</a></li>
|
||||
<li><a href="#lang.constants">vbYesNo</a></li>
|
||||
<li><a href="#lang.constants">vbYesNoCancel</a></li>
|
||||
<li><a href="#ctrl.vbox">Vertical Layout</a></li>
|
||||
<li><a href="#ide.menu.view">View Menu</a></li>
|
||||
<li><a href="#ide.overview">Visual Basic</a></li>
|
||||
<li><a href="#ctrl.terminal">VT100</a></li>
|
||||
<li><a href="#ide.debug.watch">Watch Expressions</a></li>
|
||||
<li><a href="#ide.debug.watch">Watch Window</a></li>
|
||||
<li><a href="#lang.loops">WEND</a></li>
|
||||
<li><a href="#lang.loops">WHILE</a></li>
|
||||
<li><a href="#ide.toolbox">Widget Palette</a></li>
|
||||
<li><a href="#ctrl.form">Window</a></li>
|
||||
<li><a href="#ide.menu.window">Window Menu</a></li>
|
||||
<li><a href="#lang.forms">With</a></li>
|
||||
<li><a href="#ctrl.wrapbox">WrapBox</a></li>
|
||||
<li><a href="#ctrl.wrapbox">Flow Layout</a></li>
|
||||
<li><a href="#lang.fileio">WRITE #</a></li>
|
||||
<li><a href="#ide.designer">WYSIWYG</a></li>
|
||||
<li><a href="#lang.operators">XOR</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
<main>
|
||||
|
|
|
|||
|
|
@ -36,18 +36,18 @@ img { max-width: 100%; }
|
|||
</ul>
|
||||
<h3>Index</h3>
|
||||
<ul>
|
||||
<li><a href="#help.overview">DVX Help</a></li>
|
||||
<li><a href="#help.overview">Help Viewer</a></li>
|
||||
<li><a href="#help.format">.dhs</a></li>
|
||||
<li><a href="#help.format">Source Format</a></li>
|
||||
<li><a href="#help.format">Directives</a></li>
|
||||
<li><a href="#help.compiler">dvxhlpc</a></li>
|
||||
<li><a href="#help.compiler">Compiler</a></li>
|
||||
<li><a href="#help.integration">F1</a></li>
|
||||
<li><a href="#help.integration">Context Help</a></li>
|
||||
<li><a href="#help.format">Directives</a></li>
|
||||
<li><a href="#help.overview">DVX Help</a></li>
|
||||
<li><a href="#help.compiler">dvxhlpc</a></li>
|
||||
<li><a href="#help.integration">F1</a></li>
|
||||
<li><a href="#help.overview">Help Viewer</a></li>
|
||||
<li><a href="#help.integration">helpFile</a></li>
|
||||
<li><a href="#help.integration">helpTopic</a></li>
|
||||
<li><a href="#help.integration">shellLoadAppWithArgs</a></li>
|
||||
<li><a href="#help.format">Source Format</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
<main>
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -142,10 +142,15 @@ $(BINDIR)/kpunch/widshow: ; mkdir -p $@
|
|||
|
||||
# Header dependencies
|
||||
COMMON_H = ../../libs/kpunch/libdvx/dvxApp.h ../../libs/kpunch/libdvx/dvxDlg.h ../../libs/kpunch/libdvx/dvxWgt.h ../../libs/kpunch/libdvx/dvxWm.h ../../libs/kpunch/libdvx/dvxVideo.h ../../libs/kpunch/dvxshell/shellApp.h
|
||||
# Every widget public header. Apps that pull in widget APIs (e.g. dvxdemo)
|
||||
# must rebuild when these change, otherwise the struct-layout contract
|
||||
# between the widget DXE's sApi and the app's cast to RadioApiT/etc.
|
||||
# silently drifts and produces late-binding crashes.
|
||||
WIDGET_H = $(wildcard ../../widgets/kpunch/*/*.h)
|
||||
$(OBJDIR)/cpanel.o: cpanel/cpanel.c $(COMMON_H) ../../libs/kpunch/libdvx/dvxPrefs.h ../../libs/kpunch/libdvx/platform/dvxPlat.h
|
||||
$(OBJDIR)/progman.o: progman/progman.c $(COMMON_H) ../../libs/kpunch/dvxshell/shellInf.h
|
||||
$(OBJDIR)/clock.o: clock/clock.c ../../libs/kpunch/libdvx/dvxApp.h ../../libs/kpunch/libdvx/dvxWgt.h ../../libs/kpunch/libdvx/dvxDraw.h ../../libs/kpunch/libdvx/dvxVideo.h ../../libs/kpunch/dvxshell/shellApp.h ../../libs/kpunch/libtasks/taskSwch.h
|
||||
$(OBJDIR)/dvxdemo.o: dvxdemo/dvxdemo.c $(COMMON_H)
|
||||
$(OBJDIR)/dvxdemo.o: dvxdemo/dvxdemo.c $(COMMON_H) $(WIDGET_H)
|
||||
|
||||
clean:
|
||||
rm -f $(OBJDIR)/*.o
|
||||
|
|
|
|||
|
|
@ -162,7 +162,6 @@ End
|
|||
' IN THE SOFTWARE.
|
||||
|
||||
|
||||
|
||||
OPTION EXPLICIT
|
||||
|
||||
TYPE PointT
|
||||
|
|
@ -185,22 +184,6 @@ dynCount = 0
|
|||
timerWin = 0
|
||||
tickCount = 0
|
||||
|
||||
|
||||
' ============================================================
|
||||
' OutArea helpers
|
||||
' ============================================================
|
||||
|
||||
SUB Say(s AS STRING)
|
||||
OutArea.AppendText s + CHR$(10)
|
||||
END SUB
|
||||
|
||||
|
||||
SUB Header(title AS STRING)
|
||||
Say ""
|
||||
Say "--- " + title + " ---"
|
||||
END SUB
|
||||
|
||||
|
||||
Load BasicDemo
|
||||
BasicDemo.Show
|
||||
|
||||
|
|
@ -212,9 +195,16 @@ Say "Check the Demos menu for graphics, dynamic UI, and timer demos."
|
|||
Say ""
|
||||
|
||||
|
||||
' ============================================================
|
||||
' Menu handlers
|
||||
' ============================================================
|
||||
SUB Say(s AS STRING)
|
||||
OutArea.AppendText s + CHR$(10)
|
||||
END SUB
|
||||
|
||||
|
||||
SUB Header(title AS STRING)
|
||||
Say ""
|
||||
Say "--- " + title + " ---"
|
||||
END SUB
|
||||
|
||||
|
||||
SUB mnuClear_Click
|
||||
OutArea.Text = ""
|
||||
|
|
@ -266,12 +256,8 @@ SUB mnuAbout_Click
|
|||
MsgBox msg, vbOKOnly, "About"
|
||||
END SUB
|
||||
|
||||
|
||||
' ============================================================
|
||||
' Types: INTEGER, LONG, SINGLE, DOUBLE, STRING, BOOLEAN
|
||||
' ============================================================
|
||||
|
||||
SUB btnTypes_Click
|
||||
' Types: INTEGER, LONG, SINGLE, DOUBLE, STRING, BOOLEAN
|
||||
Header "Types"
|
||||
|
||||
DIM i AS INTEGER
|
||||
|
|
@ -302,12 +288,8 @@ SUB btnTypes_Click
|
|||
LblStatus.Caption = "Types demo complete."
|
||||
END SUB
|
||||
|
||||
|
||||
' ============================================================
|
||||
' Math: integer + float operators, built-in functions
|
||||
' ============================================================
|
||||
|
||||
SUB btnMath_Click
|
||||
' Math: integer + float operators, built-in functions
|
||||
Header "Math"
|
||||
|
||||
DIM a AS INTEGER
|
||||
|
|
@ -342,12 +324,8 @@ SUB btnMath_Click
|
|||
LblStatus.Caption = "Math demo complete."
|
||||
END SUB
|
||||
|
||||
|
||||
' ============================================================
|
||||
' Strings: concatenation, LEFT$/RIGHT$/MID$/LEN/INSTR/UCASE$/LCASE$
|
||||
' ============================================================
|
||||
|
||||
SUB btnStrings_Click
|
||||
' Strings: concatenation, LEFT$/RIGHT$/MID$/LEN/INSTR/UCASE$/LCASE$
|
||||
Header "Strings"
|
||||
|
||||
DIM s AS STRING
|
||||
|
|
@ -371,12 +349,8 @@ SUB btnStrings_Click
|
|||
LblStatus.Caption = "Strings demo complete."
|
||||
END SUB
|
||||
|
||||
|
||||
' ============================================================
|
||||
' Arrays: 1D, 2D, LBOUND/UBOUND, REDIM PRESERVE
|
||||
' ============================================================
|
||||
|
||||
SUB btnArrays_Click
|
||||
' Arrays: 1D, 2D, LBOUND/UBOUND, REDIM PRESERVE
|
||||
Header "Arrays"
|
||||
|
||||
' 1D array
|
||||
|
|
@ -430,12 +404,8 @@ SUB btnArrays_Click
|
|||
LblStatus.Caption = "Arrays demo complete."
|
||||
END SUB
|
||||
|
||||
|
||||
' ============================================================
|
||||
' DATA / READ / RESTORE
|
||||
' ============================================================
|
||||
|
||||
SUB btnData_Click
|
||||
' DATA / READ / RESTORE
|
||||
Header "DATA / READ / RESTORE"
|
||||
|
||||
DATA "Red", 255, 0, 0
|
||||
|
|
@ -465,12 +435,8 @@ SUB btnData_Click
|
|||
LblStatus.Caption = "DATA/READ demo complete."
|
||||
END SUB
|
||||
|
||||
|
||||
' ============================================================
|
||||
' Control flow: IF, SELECT CASE, FOR, DO WHILE, GOSUB
|
||||
' ============================================================
|
||||
|
||||
SUB btnFlow_Click
|
||||
' Control flow: IF, SELECT CASE, FOR, DO WHILE, GOSUB
|
||||
Header "Control Flow"
|
||||
|
||||
' FOR with STEP
|
||||
|
|
@ -538,12 +504,8 @@ SUB btnFlow_Click
|
|||
RETURN
|
||||
END SUB
|
||||
|
||||
|
||||
' ============================================================
|
||||
' User-Defined Type
|
||||
' ============================================================
|
||||
|
||||
SUB btnUdt_Click
|
||||
' User-Defined Type
|
||||
Header "User-Defined Type"
|
||||
|
||||
DIM p AS PointT
|
||||
|
|
@ -566,11 +528,6 @@ SUB btnUdt_Click
|
|||
LblStatus.Caption = "UDT demo complete."
|
||||
END SUB
|
||||
|
||||
|
||||
' ============================================================
|
||||
' Optional parameters (DVX extension)
|
||||
' ============================================================
|
||||
|
||||
FUNCTION Greet(who AS STRING, OPTIONAL greeting AS STRING) AS STRING
|
||||
IF greeting = "" THEN
|
||||
greeting = "Hello"
|
||||
|
|
@ -589,12 +546,8 @@ SUB btnOpt_Click
|
|||
LblStatus.Caption = "Optional params demo complete."
|
||||
END SUB
|
||||
|
||||
|
||||
' ============================================================
|
||||
' ON ERROR GOTO
|
||||
' ============================================================
|
||||
|
||||
SUB btnError_Click
|
||||
' ON ERROR GOTO
|
||||
Header "ON ERROR GOTO"
|
||||
|
||||
ON ERROR GOTO handler
|
||||
|
|
@ -613,12 +566,8 @@ SUB btnError_Click
|
|||
LblStatus.Caption = "Error handler ran successfully."
|
||||
END SUB
|
||||
|
||||
|
||||
' ============================================================
|
||||
' PRINT USING / FORMAT$
|
||||
' ============================================================
|
||||
|
||||
SUB btnFormat_Click
|
||||
' PRINT USING / FORMAT$
|
||||
Header "Formatting"
|
||||
|
||||
Say "FORMAT$(1234.5, '#,##0.00') = " + FORMAT$(1234.5, "#,##0.00")
|
||||
|
|
@ -629,12 +578,8 @@ SUB btnFormat_Click
|
|||
LblStatus.Caption = "Format demo complete."
|
||||
END SUB
|
||||
|
||||
|
||||
' ============================================================
|
||||
' File I/O
|
||||
' ============================================================
|
||||
|
||||
SUB btnFileIO_Click
|
||||
' File I/O
|
||||
Header "File I/O"
|
||||
|
||||
DIM path AS STRING
|
||||
|
|
@ -664,12 +609,8 @@ SUB btnFileIO_Click
|
|||
LblStatus.Caption = "File I/O demo complete."
|
||||
END SUB
|
||||
|
||||
|
||||
' ============================================================
|
||||
' System: App object, environment, current directory
|
||||
' ============================================================
|
||||
|
||||
SUB btnSystem_Click
|
||||
' System: App object, environment, current directory
|
||||
Header "System / App"
|
||||
|
||||
Say "App.Path = " + App.Path
|
||||
|
|
@ -683,12 +624,8 @@ SUB btnSystem_Click
|
|||
LblStatus.Caption = "System demo complete."
|
||||
END SUB
|
||||
|
||||
|
||||
' ============================================================
|
||||
' INI read/write
|
||||
' ============================================================
|
||||
|
||||
SUB btnIni_Click
|
||||
' INI read/write
|
||||
Header "INI Read/Write"
|
||||
|
||||
DIM path AS STRING
|
||||
|
|
@ -710,11 +647,6 @@ SUB btnIni_Click
|
|||
LblStatus.Caption = "INI demo complete."
|
||||
END SUB
|
||||
|
||||
|
||||
' ============================================================
|
||||
' Dialog demos (spawns the real dialogs)
|
||||
' ============================================================
|
||||
|
||||
SUB btnDialogs_Click
|
||||
Header "Dialogs"
|
||||
|
||||
|
|
@ -750,11 +682,6 @@ SUB btnClear_Click
|
|||
mnuClear_Click
|
||||
END SUB
|
||||
|
||||
|
||||
' ============================================================
|
||||
' Graphics demo (opens a second form with Canvas)
|
||||
' ============================================================
|
||||
|
||||
SUB mnuGraphics_Click
|
||||
IF gfxWin <> 0 THEN
|
||||
EXIT SUB
|
||||
|
|
@ -766,7 +693,7 @@ SUB mnuGraphics_Click
|
|||
gfxWin = frm
|
||||
|
||||
DIM cv AS LONG
|
||||
SET cv = CreateControl(frm, "Canvas", "GfxCanvas")
|
||||
SET cv = CreateControl(frm, "PictureBox", "GfxCanvas")
|
||||
GfxCanvas.Width = 340
|
||||
GfxCanvas.Height = 260
|
||||
GfxCanvas.Weight = 1
|
||||
|
|
@ -866,11 +793,6 @@ SUB GraphicsForm_Unload
|
|||
gfxWin = 0
|
||||
END SUB
|
||||
|
||||
|
||||
' ============================================================
|
||||
' Dynamic form demo
|
||||
' ============================================================
|
||||
|
||||
SUB mnuDynamic_Click
|
||||
IF dynForm <> 0 THEN
|
||||
EXIT SUB
|
||||
|
|
@ -922,11 +844,6 @@ SUB DynForm_Unload
|
|||
dynCount = 0
|
||||
END SUB
|
||||
|
||||
|
||||
' ============================================================
|
||||
' Timer demo
|
||||
' ============================================================
|
||||
|
||||
SUB mnuTimer_Click
|
||||
IF timerWin <> 0 THEN
|
||||
EXIT SUB
|
||||
|
|
|
|||
|
|
@ -996,9 +996,14 @@ static void scanThemes(void) {
|
|||
nameLen = (int32_t)sizeof(entry.name) - 1;
|
||||
}
|
||||
|
||||
// entry.name is the extension-stripped label shown in the
|
||||
// dropdown. entry.path must use the ORIGINAL filename (with
|
||||
// extension) so dvxLoadTheme can open it -- previously we
|
||||
// used entry.name here, which pointed at a path missing
|
||||
// .THEME and silently failed.
|
||||
memcpy(entry.name, names[i], nameLen);
|
||||
entry.name[nameLen] = '\0';
|
||||
snprintf(entry.path, sizeof(entry.path), "%s" DVX_PATH_SEP "%s", THEME_DIR, entry.name);
|
||||
snprintf(entry.path, sizeof(entry.path), "%s" DVX_PATH_SEP "%s", THEME_DIR, names[i]);
|
||||
arrput(sThemeEntries, entry);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -202,6 +202,7 @@ static void parseKill(BasParserT *p);
|
|||
static void parseLineInput(BasParserT *p);
|
||||
static void parseMkDir(BasParserT *p);
|
||||
static void parseModule(BasParserT *p);
|
||||
static void prescanSignatures(BasParserT *p);
|
||||
static void parseMulExpr(BasParserT *p);
|
||||
static void parseName(BasParserT *p);
|
||||
static void parseNotExpr(BasParserT *p);
|
||||
|
|
@ -2911,6 +2912,12 @@ static void parseFunction(BasParserT *p) {
|
|||
funcSym->paramOptional[i] = paramOptional[i];
|
||||
}
|
||||
|
||||
// Record the owning form -- see parseSub for the rationale.
|
||||
if (p->sym.inFormScope && p->sym.formScopeName[0]) {
|
||||
strncpy(funcSym->formName, p->sym.formScopeName, BAS_MAX_SYMBOL_NAME - 1);
|
||||
funcSym->formName[BAS_MAX_SYMBOL_NAME - 1] = '\0';
|
||||
}
|
||||
|
||||
// Backpatch any forward-reference calls to this function
|
||||
patchCallAddrs(p, funcSym);
|
||||
|
||||
|
|
@ -3370,7 +3377,193 @@ static void parseMkDir(BasParserT *p) {
|
|||
}
|
||||
|
||||
|
||||
// Walk the token stream from current position, find every
|
||||
// top-level SUB/FUNCTION declaration, extract the signature
|
||||
// (name, params, return type), and register it in the symbol
|
||||
// table. Does not emit code. Saves and restores lexer position
|
||||
// so the main parse pass starts from the same point. This gives
|
||||
// VB-style forward visibility: call sites that appear earlier in
|
||||
// the source than the SUB definition still resolve to the right
|
||||
// paramCount / types.
|
||||
static void prescanSignatures(BasParserT *p) {
|
||||
BasLexerT savedLex = p->lex;
|
||||
bool savedErr = p->hasError;
|
||||
int32_t savedErrLn = p->errorLine;
|
||||
char savedErrMsg[BAS_PARSE_ERR_SCRATCH];
|
||||
snprintf(savedErrMsg, sizeof(savedErrMsg), "%s", p->error);
|
||||
|
||||
while (!check(p, TOK_EOF)) {
|
||||
// "END SUB" / "END FUNCTION" consume the END and the following
|
||||
// keyword as separate tokens; skip END so the next-iteration
|
||||
// SUB/FUNCTION check doesn't misinterpret it as a declaration.
|
||||
if (check(p, TOK_END)) {
|
||||
advance(p);
|
||||
continue;
|
||||
}
|
||||
|
||||
bool isFn = check(p, TOK_FUNCTION);
|
||||
bool isSub = check(p, TOK_SUB);
|
||||
|
||||
if (!isFn && !isSub) {
|
||||
advance(p);
|
||||
continue;
|
||||
}
|
||||
|
||||
advance(p); // consume SUB / FUNCTION
|
||||
|
||||
if (!check(p, TOK_IDENT)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
char name[BAS_MAX_TOKEN_LEN];
|
||||
strncpy(name, p->lex.token.text, BAS_MAX_TOKEN_LEN - 1);
|
||||
name[BAS_MAX_TOKEN_LEN - 1] = '\0';
|
||||
advance(p);
|
||||
|
||||
// Param list
|
||||
int32_t paramCount = 0;
|
||||
int32_t requiredCount = 0;
|
||||
uint8_t paramTypes[BAS_MAX_PARAMS] = {0};
|
||||
bool paramByVal[BAS_MAX_PARAMS] = {0};
|
||||
bool paramOptional[BAS_MAX_PARAMS] = {0};
|
||||
|
||||
if (check(p, TOK_LPAREN)) {
|
||||
advance(p);
|
||||
|
||||
while (!check(p, TOK_RPAREN) && !check(p, TOK_EOF) && !check(p, TOK_NEWLINE)) {
|
||||
if (paramCount > 0) {
|
||||
if (!check(p, TOK_COMMA)) {
|
||||
break;
|
||||
}
|
||||
advance(p);
|
||||
}
|
||||
|
||||
bool optional = false;
|
||||
|
||||
if (check(p, TOK_OPTIONAL)) {
|
||||
optional = true;
|
||||
advance(p);
|
||||
}
|
||||
|
||||
bool byVal = false;
|
||||
|
||||
if (check(p, TOK_BYVAL)) {
|
||||
byVal = true;
|
||||
advance(p);
|
||||
}
|
||||
|
||||
if (!check(p, TOK_IDENT)) {
|
||||
break;
|
||||
}
|
||||
|
||||
char paramName[BAS_MAX_TOKEN_LEN];
|
||||
strncpy(paramName, p->lex.token.text, BAS_MAX_TOKEN_LEN - 1);
|
||||
paramName[BAS_MAX_TOKEN_LEN - 1] = '\0';
|
||||
advance(p);
|
||||
|
||||
uint8_t pdt = suffixToType(paramName);
|
||||
|
||||
if (check(p, TOK_AS)) {
|
||||
advance(p);
|
||||
// resolveTypeName sets hasError on miss; we clear
|
||||
// at function end so one broken signature doesn't
|
||||
// stop us from discovering the rest.
|
||||
pdt = resolveTypeName(p);
|
||||
|
||||
if (p->hasError) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (paramCount < BAS_MAX_PARAMS) {
|
||||
paramTypes[paramCount] = pdt;
|
||||
paramByVal[paramCount] = byVal;
|
||||
paramOptional[paramCount] = optional;
|
||||
}
|
||||
|
||||
if (!optional) {
|
||||
requiredCount = paramCount + 1;
|
||||
}
|
||||
|
||||
paramCount++;
|
||||
}
|
||||
|
||||
if (check(p, TOK_RPAREN)) {
|
||||
advance(p);
|
||||
}
|
||||
}
|
||||
|
||||
// FUNCTION return type (AS clause; or suffix on name)
|
||||
uint8_t returnType = suffixToType(name);
|
||||
|
||||
if (isFn && check(p, TOK_AS)) {
|
||||
advance(p);
|
||||
returnType = resolveTypeName(p);
|
||||
}
|
||||
|
||||
// Register / update the symbol. If a call site already
|
||||
// created a forward-ref stub, update it in place.
|
||||
BasSymbolT *sym = basSymTabFindGlobal(&p->sym, name);
|
||||
|
||||
if (sym == NULL) {
|
||||
bool savedLocal = p->sym.inLocalScope;
|
||||
p->sym.inLocalScope = false;
|
||||
sym = basSymTabAdd(&p->sym, name,
|
||||
isFn ? SYM_FUNCTION : SYM_SUB,
|
||||
returnType);
|
||||
p->sym.inLocalScope = savedLocal;
|
||||
}
|
||||
|
||||
if (sym != NULL) {
|
||||
sym->scope = SCOPE_GLOBAL;
|
||||
sym->dataType = returnType;
|
||||
sym->paramCount = paramCount;
|
||||
sym->requiredParams = requiredCount;
|
||||
|
||||
for (int32_t i = 0; i < paramCount && i < BAS_MAX_PARAMS; i++) {
|
||||
sym->paramTypes[i] = paramTypes[i];
|
||||
sym->paramByVal[i] = paramByVal[i];
|
||||
sym->paramOptional[i] = paramOptional[i];
|
||||
}
|
||||
|
||||
// basSymTabAdd defaults isDefined=true; clear it so
|
||||
// call sites that encounter this symbol before the real
|
||||
// body is parsed register themselves as forward-refs
|
||||
// (patchAddrs). The real parseSub/parseFunction pass
|
||||
// sets isDefined=true and fills in codeAddr, at which
|
||||
// point patchCallAddrs backpatches the forward refs.
|
||||
sym->isDefined = false;
|
||||
sym->codeAddr = 0;
|
||||
}
|
||||
|
||||
// Best-effort: clear any scan error so we continue to the
|
||||
// next declaration. The main parse pass will re-surface
|
||||
// real errors with full location info.
|
||||
if (p->hasError) {
|
||||
p->hasError = false;
|
||||
p->errorLine = 0;
|
||||
p->error[0] = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
p->lex = savedLex;
|
||||
p->hasError = savedErr;
|
||||
p->errorLine = savedErrLn;
|
||||
snprintf(p->error, sizeof(p->error), "%s", savedErrMsg);
|
||||
}
|
||||
|
||||
|
||||
static void parseModule(BasParserT *p) {
|
||||
// VB semantics: all SUB/FUNCTION declarations are visible from
|
||||
// anywhere in the module regardless of source order. Do a
|
||||
// pre-scan that walks the token stream, extracts every
|
||||
// SUB/FUNCTION signature, and registers it in the symbol table.
|
||||
// Call sites encountered later (including module-level code
|
||||
// that precedes the SUB definition in source) can then validate
|
||||
// argument count / types against the real signature instead of
|
||||
// falling back to a paramCount=0 placeholder.
|
||||
prescanSignatures(p);
|
||||
|
||||
skipNewlines(p);
|
||||
|
||||
while (!p->hasError && !check(p, TOK_EOF)) {
|
||||
|
|
@ -5843,6 +6036,16 @@ static void parseSub(BasParserT *p) {
|
|||
subSym->paramOptional[i] = paramOptional[i];
|
||||
}
|
||||
|
||||
// Record the owning form so fireCtrlEvent can bind the SUB's
|
||||
// form-scope variables at call time. Prescan adds SUB symbols
|
||||
// before the BEGINFORM directive is consumed, so the formName
|
||||
// wasn't populated by basSymTabAdd; set it here once we know we
|
||||
// are inside a form scope.
|
||||
if (p->sym.inFormScope && p->sym.formScopeName[0]) {
|
||||
strncpy(subSym->formName, p->sym.formScopeName, BAS_MAX_SYMBOL_NAME - 1);
|
||||
subSym->formName[BAS_MAX_SYMBOL_NAME - 1] = '\0';
|
||||
}
|
||||
|
||||
// Backpatch any forward-reference calls to this sub
|
||||
patchCallAddrs(p, subSym);
|
||||
|
||||
|
|
|
|||
|
|
@ -34,7 +34,14 @@
|
|||
#include "dvxRes.h"
|
||||
#include "dvxWm.h"
|
||||
#include "shellApp.h"
|
||||
#include "box/box.h"
|
||||
// Widget headers below publish inline accessor macros over
|
||||
// dvx*Api() -> wgtGetApi(...) lookups, so they're lightweight
|
||||
// compile-time type-safety over a runtime-dynamic dispatch -- no
|
||||
// link-time dependency on the widget implementation. AnsiTerm,
|
||||
// DataCtrl, and DbGrid need direct typed calls because their C
|
||||
// APIs take callback function pointers, peer widget pointers, or
|
||||
// string-returning-from-string signatures that don't fit the
|
||||
// basic-callable iface-method dispatch.
|
||||
#include "ansiTerm/ansiTerm.h"
|
||||
#include "dataCtrl/dataCtrl.h"
|
||||
#include "dbGrid/dbGrid.h"
|
||||
|
|
@ -60,6 +67,16 @@
|
|||
// Module-level form runtime pointer for onFormClose callback
|
||||
static BasFormRtT *sFormRt = NULL;
|
||||
|
||||
// True while a basFormRtLoadFrm is in progress. The .frm parser calls
|
||||
// basFormRtLoadForm via onFormBegin to create the bare form before it
|
||||
// starts firing onCtrlBegin callbacks; if we let basFormRtLoadForm run
|
||||
// the form's init-code during that window the init fires against a
|
||||
// control list that is still empty (and then crashes with
|
||||
// "unknown control: cboSize" etc.). basFormRtLoadFrm sets this true
|
||||
// around frmParse so the nested LoadForm skips init; the outer
|
||||
// caller runs init (or the event loop) after parsing completes.
|
||||
static bool sLoadingFrm = false;
|
||||
|
||||
|
||||
// ============================================================
|
||||
// Module-scope declarations
|
||||
|
|
@ -177,6 +194,7 @@ typedef struct {
|
|||
bool checked;
|
||||
bool radioCheck;
|
||||
bool enabled;
|
||||
bool visible; // level==0: true = menu bar entry, false = popup-only
|
||||
} BasFrmMenuItemT;
|
||||
|
||||
|
||||
|
|
@ -241,6 +259,7 @@ static const BasProcEntryT *basModuleFindProc(const BasModuleT *mod, const char
|
|||
static BasFormT *resolveOwningForm(BasFormRtT *rt, const BasProcEntryT *proc);
|
||||
int32_t basPromptSave(const char *title);
|
||||
static BasValueT callCommonMethod(BasControlT *ctrl, const char *methodName, BasValueT *args, int32_t argc);
|
||||
static BasFrmPopupMenuT *findPopupMenu(BasFormT *form, const char *name);
|
||||
void CommAttach(int32_t handle, const char *termCtrlName, int32_t channel, int32_t encrypt);
|
||||
void CommClose(int32_t handle);
|
||||
void CommDetach(int32_t handle);
|
||||
|
|
@ -592,7 +611,8 @@ BasValueT basFormRtCallMethod(void *ctx, void *ctrlRef, const char *methodName,
|
|||
if (!ctrl) {
|
||||
basFormRtRuntimeError(rt,
|
||||
"Method call on unknown control",
|
||||
"Method: %s\nControl was not found on any loaded form.",
|
||||
"Control: %s\nMethod: %s\nControl was not found on any loaded form.",
|
||||
(rt && rt->lastLookupName[0]) ? rt->lastLookupName : "?",
|
||||
methodName ? methodName : "?");
|
||||
return zeroValue();
|
||||
}
|
||||
|
|
@ -765,6 +785,24 @@ BasValueT basFormRtCallMethod(void *ctx, void *ctrlRef, const char *methodName,
|
|||
basStringUnref(s);
|
||||
}
|
||||
return zeroValue();
|
||||
|
||||
case WGT_SIG_STR_STR:
|
||||
if (argc >= 2) {
|
||||
BasStringT *a = basValFormatString(args[0]);
|
||||
BasStringT *b = basValFormatString(args[1]);
|
||||
((void (*)(WidgetT *, const char *, const char *))m->fn)(w, a->data, b->data);
|
||||
basStringUnref(a);
|
||||
basStringUnref(b);
|
||||
}
|
||||
return zeroValue();
|
||||
|
||||
case WGT_SIG_STR_BOOL:
|
||||
if (argc >= 2) {
|
||||
BasStringT *s = basValFormatString(args[0]);
|
||||
((void (*)(WidgetT *, const char *, bool))m->fn)(w, s->data, basValIsTruthy(args[1]));
|
||||
basStringUnref(s);
|
||||
}
|
||||
return zeroValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1131,6 +1169,12 @@ void basFormRtDestroy(BasFormRtT *rt) {
|
|||
|
||||
arrfree(form->menuIdMap);
|
||||
|
||||
for (int32_t j = 0; j < form->popupMenuCount; j++) {
|
||||
wmFreeMenu(form->popupMenus[j].menu);
|
||||
}
|
||||
|
||||
arrfree(form->popupMenus);
|
||||
|
||||
if (form->window) {
|
||||
dvxDestroyWindow(rt->ctx, form->window);
|
||||
form->window = NULL;
|
||||
|
|
@ -1168,6 +1212,12 @@ void *basFormRtFindCtrl(void *ctx, void *formRef, const char *ctrlName) {
|
|||
BasFormRtT *rt = (BasFormRtT *)ctx;
|
||||
BasFormT *form = (BasFormT *)formRef;
|
||||
|
||||
// Remember the last name asked for so a subsequent method/prop
|
||||
// call that lands on a NULL ctrl can still name the culprit.
|
||||
if (rt && ctrlName) {
|
||||
snprintf(rt->lastLookupName, sizeof(rt->lastLookupName), "%s", ctrlName);
|
||||
}
|
||||
|
||||
if (!form) {
|
||||
return NULL;
|
||||
}
|
||||
|
|
@ -1184,6 +1234,8 @@ void *basFormRtFindCtrl(void *ctx, void *formRef, const char *ctrlName) {
|
|||
}
|
||||
}
|
||||
|
||||
// (local miss is expected for cross-form access; don't spam the log)
|
||||
|
||||
// Search menu items on the current form
|
||||
for (int32_t i = 0; i < form->menuIdMapCount; i++) {
|
||||
if (strcasecmp(form->menuIdMap[i].name, ctrlName) == 0) {
|
||||
|
|
@ -1243,9 +1295,14 @@ void *basFormRtFindCtrl(void *ctx, void *formRef, const char *ctrlName) {
|
|||
|
||||
|
||||
void *basFormRtFindCtrlIdx(void *ctx, void *formRef, const char *ctrlName, int32_t index) {
|
||||
(void)ctx;
|
||||
BasFormRtT *rt = (BasFormRtT *)ctx;
|
||||
BasFormT *form = (BasFormT *)formRef;
|
||||
|
||||
if (rt && ctrlName) {
|
||||
snprintf(rt->lastLookupName, sizeof(rt->lastLookupName),
|
||||
"%s(%d)", ctrlName, (int)index);
|
||||
}
|
||||
|
||||
if (!form) {
|
||||
return NULL;
|
||||
}
|
||||
|
|
@ -1385,7 +1442,8 @@ BasValueT basFormRtGetProp(void *ctx, void *ctrlRef, const char *propName) {
|
|||
if (!ctrl) {
|
||||
basFormRtRuntimeError(rt,
|
||||
"Read of unknown control",
|
||||
"Property: %s\nControl was not found on any loaded form.",
|
||||
"Control: %s\nProperty: %s\nControl was not found on any loaded form.",
|
||||
(rt && rt->lastLookupName[0]) ? rt->lastLookupName : "?",
|
||||
propName ? propName : "?");
|
||||
return zeroValue();
|
||||
}
|
||||
|
|
@ -1667,12 +1725,11 @@ void *basFormRtLoadForm(void *ctx, const char *formName) {
|
|||
}
|
||||
|
||||
// Check the .frm cache for reload after unload.
|
||||
// Use a static guard to prevent recursion: basFormRtLoadFrm calls
|
||||
// basFormRtLoadForm for the "Begin Form" line, which would re-enter
|
||||
// this function. The guard lets the recursive call fall through to
|
||||
// bare form creation, which basFormRtLoadFrm then populates.
|
||||
static bool sLoadingFrm = false;
|
||||
|
||||
// sLoadingFrm (module-scope) prevents recursion: basFormRtLoadFrm
|
||||
// calls basFormRtLoadForm via frmLoad_onFormBegin, which would
|
||||
// re-enter this function. The guard lets the recursive call fall
|
||||
// through to bare form creation, which basFormRtLoadFrm then
|
||||
// populates.
|
||||
if (!sLoadingFrm) {
|
||||
for (int32_t i = 0; i < rt->frmCacheCount; i++) {
|
||||
if (strcasecmp(rt->frmCache[i].formName, formName) == 0) {
|
||||
|
|
@ -1786,7 +1843,19 @@ BasFormT *basFormRtLoadFrm(BasFormRtT *rt, const char *source, int32_t sourceLen
|
|||
cbs.onCtrlEnd = frmLoad_onCtrlEnd;
|
||||
cbs.onCtrlProp = frmLoad_onCtrlProp;
|
||||
|
||||
if (!frmParse(source, sourceLen, &cbs)) {
|
||||
// Set the guard so nested basFormRtLoadForm (fired from
|
||||
// frmLoad_onFormBegin) skips the form's init code -- running it
|
||||
// here would fire against an empty control list. The outer
|
||||
// caller (IDE runModule or basFormRtLoadAllForms) runs module-
|
||||
// level / init code after parsing finishes. Save+restore so
|
||||
// nested basFormRtLoadForm -> basFormRtLoadFrm reentry still
|
||||
// terminates the inner guard correctly.
|
||||
bool savedLoadingFrm = sLoadingFrm;
|
||||
sLoadingFrm = true;
|
||||
bool parsed = frmParse(source, sourceLen, &cbs);
|
||||
sLoadingFrm = savedLoadingFrm;
|
||||
|
||||
if (!parsed) {
|
||||
arrfree(ctx.menuItems);
|
||||
return NULL;
|
||||
}
|
||||
|
|
@ -1801,16 +1870,33 @@ BasFormT *basFormRtLoadFrm(BasFormRtT *rt, const char *source, int32_t sourceLen
|
|||
form->contentBox = basFormRtCreateContentBox(form->root, form->frmLayout);
|
||||
}
|
||||
|
||||
// Build menu bar from accumulated menu items
|
||||
// Build menu bar + popup menus from accumulated menu items.
|
||||
// A top-level Menu with Visible=False becomes a standalone
|
||||
// popup (stored by name on the form). All other top-level
|
||||
// menus go on the window's menu bar. Nested items are added
|
||||
// to whichever top-level (bar or popup) is currently active.
|
||||
int32_t menuCount = (int32_t)arrlen(menuItems);
|
||||
|
||||
if (menuCount > 0 && form->window) {
|
||||
MenuBarT *bar = wmAddMenuBar(form->window);
|
||||
MenuBarT *bar = NULL;
|
||||
bool haveVisibleTop = false;
|
||||
|
||||
for (int32_t i = 0; i < menuCount; i++) {
|
||||
if (menuItems[i].level == 0 && menuItems[i].visible) {
|
||||
haveVisibleTop = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (haveVisibleTop) {
|
||||
bar = wmAddMenuBar(form->window);
|
||||
}
|
||||
|
||||
if (bar) {
|
||||
#define MENU_ID_BASE 10000
|
||||
MenuT *menuStack[16];
|
||||
memset(menuStack, 0, sizeof(menuStack));
|
||||
bool topIsPopup = false;
|
||||
MenuT *curTopPopup = NULL;
|
||||
|
||||
for (int32_t i = 0; i < menuCount; i++) {
|
||||
BasFrmMenuItemT *mi = &menuItems[i];
|
||||
|
|
@ -1818,16 +1904,31 @@ BasFormT *basFormRtLoadFrm(BasFormRtT *rt, const char *source, int32_t sourceLen
|
|||
bool isSubParent = (i + 1 < menuCount && menuItems[i + 1].level > mi->level);
|
||||
|
||||
if (mi->level == 0) {
|
||||
// Top-level menu header
|
||||
menuStack[0] = wmAddMenu(bar, mi->caption);
|
||||
} else if (isSep && mi->level > 0 && menuStack[mi->level - 1]) {
|
||||
// Separator
|
||||
if (mi->visible) {
|
||||
// Top-level menu header on the bar
|
||||
menuStack[0] = bar ? wmAddMenu(bar, mi->caption) : NULL;
|
||||
topIsPopup = false;
|
||||
curTopPopup = NULL;
|
||||
} else {
|
||||
// Popup menu: allocate standalone, store by name.
|
||||
curTopPopup = wmCreateMenu();
|
||||
menuStack[0] = curTopPopup;
|
||||
topIsPopup = true;
|
||||
|
||||
if (curTopPopup) {
|
||||
BasFrmPopupMenuT entry;
|
||||
memset(&entry, 0, sizeof(entry));
|
||||
snprintf(entry.name, BAS_MAX_CTRL_NAME, "%s", mi->name);
|
||||
entry.menu = curTopPopup;
|
||||
arrput(form->popupMenus, entry);
|
||||
form->popupMenuCount = (int32_t)arrlen(form->popupMenus);
|
||||
}
|
||||
}
|
||||
} else if (isSep && menuStack[mi->level - 1]) {
|
||||
wmAddMenuSeparator(menuStack[mi->level - 1]);
|
||||
} else if (isSubParent && mi->level > 0 && menuStack[mi->level - 1]) {
|
||||
// Submenu parent
|
||||
} else if (isSubParent && menuStack[mi->level - 1]) {
|
||||
menuStack[mi->level] = wmAddSubMenu(menuStack[mi->level - 1], mi->caption);
|
||||
} else if (mi->level > 0 && menuStack[mi->level - 1]) {
|
||||
// Regular menu item
|
||||
} else if (menuStack[mi->level - 1]) {
|
||||
int32_t id = MENU_ID_BASE + i;
|
||||
|
||||
if (mi->radioCheck) {
|
||||
|
|
@ -1838,11 +1939,14 @@ BasFormT *basFormRtLoadFrm(BasFormRtT *rt, const char *source, int32_t sourceLen
|
|||
wmAddMenuItem(menuStack[mi->level - 1], mi->caption, id);
|
||||
}
|
||||
|
||||
if (!mi->enabled) {
|
||||
if (!mi->enabled && bar && !topIsPopup) {
|
||||
wmMenuItemSetEnabled(bar, id, false);
|
||||
}
|
||||
|
||||
// Store ID-to-name mapping for event dispatch
|
||||
// Store ID-to-name mapping for event dispatch.
|
||||
// Popup menu items use the same table -- the
|
||||
// window's onMenu handler looks up by ID
|
||||
// regardless of which menu the click came from.
|
||||
BasMenuIdMapT map;
|
||||
memset(&map, 0, sizeof(map));
|
||||
map.id = id;
|
||||
|
|
@ -1854,7 +1958,6 @@ BasFormT *basFormRtLoadFrm(BasFormRtT *rt, const char *source, int32_t sourceLen
|
|||
|
||||
form->window->onMenu = onFormMenu;
|
||||
}
|
||||
}
|
||||
|
||||
arrfree(menuItems);
|
||||
menuItems = NULL;
|
||||
|
|
@ -2021,6 +2124,16 @@ int32_t basFormRtMsgBox(void *ctx, const char *message, int32_t flags, const cha
|
|||
// control type, missing control reference, unknown method, etc.
|
||||
// Silent no-ops here would hide these failures from the developer.
|
||||
void basFormRtRuntimeError(BasFormRtT *rt, const char *summary, const char *detailFmt, ...) {
|
||||
// If we're already in a terminated state, swallow follow-on errors
|
||||
// silently. The modal message box pumps events, which can re-enter
|
||||
// the form runtime and trip the same missing-control / missing-
|
||||
// method path for every queued event. Showing the dialog again
|
||||
// for each one stacks modals and spams the log; the first report
|
||||
// is enough to point the user at the root cause.
|
||||
if (rt && rt->terminated) {
|
||||
return;
|
||||
}
|
||||
|
||||
char details[512];
|
||||
va_list ap;
|
||||
|
||||
|
|
@ -2028,8 +2141,11 @@ void basFormRtRuntimeError(BasFormRtT *rt, const char *summary, const char *deta
|
|||
vsnprintf(details, sizeof(details), detailFmt, ap);
|
||||
va_end(ap);
|
||||
|
||||
// Log with a blank line and indent so it stands out among normal
|
||||
// log traffic.
|
||||
// Log the error only when we're also going to show the modal
|
||||
// dialog. Hosts that suppress the dialog (the IDE) do their own
|
||||
// reporting via the Output pane and editor navigation, and don't
|
||||
// need a redundant log copy of every runtime error.
|
||||
if (!rt || !rt->suppressErrorDialog) {
|
||||
dvxLog("");
|
||||
dvxLog("BASIC RUNTIME ERROR: %s", summary ? summary : "(no summary)");
|
||||
|
||||
|
|
@ -2055,27 +2171,64 @@ void basFormRtRuntimeError(BasFormRtT *rt, const char *summary, const char *deta
|
|||
|
||||
p = nl + 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (rt && rt->ctx) {
|
||||
// Halt the VM and flip rt->terminated BEFORE showing the dialog.
|
||||
// dvxMessageBox pumps events while it's open, and a single missing
|
||||
// control typically has several queued events targeting it (load +
|
||||
// activate + paint + ...). If we haven't marked the runtime as
|
||||
// terminated yet, each re-entry into basFormRtRuntimeError passes
|
||||
// the early-return check and stacks another dialog. Marking first
|
||||
// means the re-entry sees terminated=true, returns early, and the
|
||||
// user only has to dismiss one dialog. Setting vm->errorMsg makes
|
||||
// basVmRun return BAS_VM_ERROR (not HALTED) so the IDE treats this
|
||||
// as a runtime error, shows the diagnostic, and navigates the
|
||||
// editor to vm->currentLine. ended=true also breaks the IDE's
|
||||
// VB-style event pump loop.
|
||||
if (rt && rt->vm) {
|
||||
BasVmT *vm = rt->vm;
|
||||
|
||||
// Format: "Summary\nDetails". No embedded "on line N:" so the
|
||||
// host (IDE) can prefix its own proc/line header without
|
||||
// having to parse the line number back out of the middle.
|
||||
// For the compiled stub, the host shows this as-is, which is
|
||||
// fine -- the details string is the interesting part.
|
||||
snprintf(vm->errorMsg, sizeof(vm->errorMsg),
|
||||
"%s\n%s",
|
||||
summary ? summary : "Runtime error", details);
|
||||
|
||||
// Snapshot PC and source line at the error site so the
|
||||
// debugger can locate the owning SUB and line. runSubLoop
|
||||
// restores vm->pc to the caller's saved PC on the way out (to
|
||||
// keep RESUME semantics clean for ON ERROR), and
|
||||
// vm->currentLine keeps being stomped by OP_LINE as other
|
||||
// queued event handlers run. Both would otherwise be wrong
|
||||
// by the time the IDE reads them after the loop exits.
|
||||
//
|
||||
// Prefer vm->currentOpPc over vm->pc: basVmStep advances
|
||||
// vm->pc past the opcode AND its operands before dispatching
|
||||
// the handler, so by the time we get here it can point at
|
||||
// the first byte of the NEXT proc, which makes the proc
|
||||
// lookup land on the wrong handler. currentOpPc is the PC
|
||||
// of the opcode that triggered the host callback.
|
||||
vm->errorPc = vm->currentOpPc > 0 ? vm->currentOpPc : vm->pc;
|
||||
vm->errorLine = vm->currentLine;
|
||||
|
||||
vm->running = false;
|
||||
vm->ended = true;
|
||||
}
|
||||
|
||||
if (rt) {
|
||||
rt->terminated = true;
|
||||
}
|
||||
|
||||
if (rt && rt->ctx && !rt->suppressErrorDialog) {
|
||||
char boxMsg[640];
|
||||
snprintf(boxMsg, sizeof(boxMsg), "%s\n\n%s",
|
||||
summary ? summary : "Runtime error",
|
||||
details);
|
||||
dvxMessageBox(rt->ctx, "BASIC Runtime Error", boxMsg, MB_OK | MB_ICONERROR);
|
||||
}
|
||||
|
||||
// Halt the VM so execution stops here rather than stumbling forward
|
||||
// with a corrupted state. Without this, a single missing control
|
||||
// can trigger a cascade of follow-on errors as subsequent property
|
||||
// accesses and method calls fail too. The `terminated` flag tells
|
||||
// basFormRtEventLoop to exit at its next pump -- otherwise the main
|
||||
// window would stay open with a dead VM behind it.
|
||||
if (rt) {
|
||||
if (rt->vm) {
|
||||
rt->vm->running = false;
|
||||
}
|
||||
rt->terminated = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -2157,10 +2310,16 @@ void basFormRtRunSimple(BasFormRtT *rt) {
|
|||
} else if (result == BAS_VM_YIELDED) {
|
||||
// DoEvents returned, continue
|
||||
} else if (result == BAS_VM_ERROR) {
|
||||
// basFormRtRuntimeError already surfaces its own error
|
||||
// dialog and sets rt->terminated, so only show this generic
|
||||
// box for VM-internal errors (division by zero, type
|
||||
// mismatch, etc.) that didn't come through that path.
|
||||
if (!rt->terminated) {
|
||||
const char *errMsg = basVmGetError(vm);
|
||||
char buf[512];
|
||||
snprintf(buf, sizeof(buf), "Runtime error:\n%s", errMsg ? errMsg : "Unknown error");
|
||||
dvxMessageBox(rt->ctx, "Error", buf, 0);
|
||||
}
|
||||
break;
|
||||
} else {
|
||||
break;
|
||||
|
|
@ -2206,7 +2365,8 @@ void basFormRtSetProp(void *ctx, void *ctrlRef, const char *propName, BasValueT
|
|||
if (!ctrl) {
|
||||
basFormRtRuntimeError(rt,
|
||||
"Assignment to unknown control",
|
||||
"Property: %s\nControl was not found on any loaded form.",
|
||||
"Control: %s\nProperty: %s\nControl was not found on any loaded form.",
|
||||
(rt && rt->lastLookupName[0]) ? rt->lastLookupName : "?",
|
||||
propName ? propName : "?");
|
||||
return;
|
||||
}
|
||||
|
|
@ -2313,9 +2473,29 @@ void basFormRtSetProp(void *ctx, void *ctrlRef, const char *propName, BasValueT
|
|||
return;
|
||||
}
|
||||
|
||||
if (strcasecmp(propName, "ContextMenu") == 0) {
|
||||
BasStringT *s = basValFormatString(value);
|
||||
MenuT *m = NULL;
|
||||
|
||||
if (s->len > 0) {
|
||||
BasFrmPopupMenuT *pm = findPopupMenu(frm, s->data);
|
||||
|
||||
if (pm) {
|
||||
m = pm->menu;
|
||||
}
|
||||
}
|
||||
|
||||
if (win) {
|
||||
win->contextMenu = m;
|
||||
}
|
||||
|
||||
basStringUnref(s);
|
||||
return;
|
||||
}
|
||||
|
||||
basFormRtRuntimeError(rt,
|
||||
"Unknown form property",
|
||||
"Form: %s\nProperty: %s\nValid: Caption, Visible, Width, Height, Left, Top, Resizable, AutoSize, Centered.",
|
||||
"Form: %s\nProperty: %s\nValid: Caption, Visible, Width, Height, Left, Top, Resizable, AutoSize, Centered, ContextMenu.",
|
||||
frm->name, propName ? propName : "?");
|
||||
return;
|
||||
}
|
||||
|
|
@ -2443,6 +2623,14 @@ void basFormRtUnloadForm(void *ctx, void *formRef) {
|
|||
arrfree(form->controls);
|
||||
form->controls = NULL;
|
||||
|
||||
for (int32_t i = 0; i < form->popupMenuCount; i++) {
|
||||
wmFreeMenu(form->popupMenus[i].menu);
|
||||
}
|
||||
|
||||
arrfree(form->popupMenus);
|
||||
form->popupMenus = NULL;
|
||||
form->popupMenuCount = 0;
|
||||
|
||||
if (form->window) {
|
||||
if (rt->ctx->modalWindow == form->window) {
|
||||
rt->ctx->modalWindow = NULL;
|
||||
|
|
@ -2555,6 +2743,40 @@ int32_t basPromptSave(const char *title) {
|
|||
}
|
||||
|
||||
|
||||
// findPopupMenu -- look up a named popup menu on a form. Returns
|
||||
// NULL if no such menu exists.
|
||||
static BasFrmPopupMenuT *findPopupMenu(BasFormT *form, const char *name) {
|
||||
if (!form || !name) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
for (int32_t i = 0; i < form->popupMenuCount; i++) {
|
||||
if (strcasecmp(form->popupMenus[i].name, name) == 0) {
|
||||
return &form->popupMenus[i];
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
// Allocate the next menu-item ID for a form. IDs are unique across
|
||||
// menu bar + popups on the same form and reused by menuIdMap for
|
||||
// event dispatch. Starts from MENU_ID_BASE + the highest in-use ID
|
||||
// so runtime-added items don't collide with .frm-loaded items.
|
||||
static int32_t nextMenuItemId(BasFormT *form) {
|
||||
int32_t maxId = 10000 - 1; // MENU_ID_BASE - 1
|
||||
|
||||
for (int32_t i = 0; i < form->menuIdMapCount; i++) {
|
||||
if (form->menuIdMap[i].id > maxId) {
|
||||
maxId = form->menuIdMap[i].id;
|
||||
}
|
||||
}
|
||||
|
||||
return maxId + 1;
|
||||
}
|
||||
|
||||
|
||||
static BasValueT callCommonMethod(BasControlT *ctrl, const char *methodName, BasValueT *args, int32_t argc) {
|
||||
if (strcasecmp(methodName, "SetFocus") == 0) {
|
||||
wgtSetFocused(ctrl->widget);
|
||||
|
|
@ -2584,6 +2806,341 @@ static BasValueT callCommonMethod(BasControlT *ctrl, const char *methodName, Bas
|
|||
return zeroValue();
|
||||
}
|
||||
|
||||
// ========================================================
|
||||
// Popup / context menu methods
|
||||
// ========================================================
|
||||
//
|
||||
// Any control (and the synthetic form control) can show, build,
|
||||
// or tear down popup menus owned by its form. Menus are named
|
||||
// by string and stored on the form; IDs are shared with the
|
||||
// menu-bar items so nameClick handlers fire regardless of which
|
||||
// surface the user picked from.
|
||||
|
||||
BasFormT *menuForm = ctrl ? ctrl->form : NULL;
|
||||
|
||||
if (strcasecmp(methodName, "PopupMenu") == 0) {
|
||||
// PopupMenu name$ [, x%, y%]
|
||||
if (!menuForm || !menuForm->window || argc < 1) {
|
||||
return zeroValue();
|
||||
}
|
||||
|
||||
BasStringT *s = basValFormatString(args[0]);
|
||||
BasFrmPopupMenuT *pm = findPopupMenu(menuForm, s->data);
|
||||
basStringUnref(s);
|
||||
|
||||
if (!pm || !pm->menu) {
|
||||
return zeroValue();
|
||||
}
|
||||
|
||||
int32_t screenX = sFormRt ? sFormRt->ctx->mouseX : 0;
|
||||
int32_t screenY = sFormRt ? sFormRt->ctx->mouseY : 0;
|
||||
|
||||
if (argc >= 3) {
|
||||
// Explicit coords are relative to the control if we have
|
||||
// one with a widget; form-level PopupMenu treats them as
|
||||
// client-area coords translated through the window.
|
||||
int32_t x = (int32_t)basValToNumber(args[1]);
|
||||
int32_t y = (int32_t)basValToNumber(args[2]);
|
||||
|
||||
if (ctrl && ctrl->widget) {
|
||||
screenX = ctrl->widget->x + x;
|
||||
screenY = ctrl->widget->y + y;
|
||||
} else if (menuForm->window) {
|
||||
screenX = menuForm->window->x + menuForm->window->contentX + x;
|
||||
screenY = menuForm->window->y + menuForm->window->contentY + y;
|
||||
}
|
||||
}
|
||||
|
||||
if (sFormRt && sFormRt->ctx) {
|
||||
dvxShowContextMenu(sFormRt->ctx, menuForm->window, pm->menu, screenX, screenY);
|
||||
}
|
||||
|
||||
return zeroValue();
|
||||
}
|
||||
|
||||
if (strcasecmp(methodName, "CreateMenu") == 0) {
|
||||
// CreateMenu name$ -- allocate an empty named popup menu.
|
||||
// No-op if a menu with that name already exists.
|
||||
if (!menuForm || argc < 1) {
|
||||
return zeroValue();
|
||||
}
|
||||
|
||||
BasStringT *s = basValFormatString(args[0]);
|
||||
|
||||
if (!findPopupMenu(menuForm, s->data)) {
|
||||
BasFrmPopupMenuT entry;
|
||||
memset(&entry, 0, sizeof(entry));
|
||||
snprintf(entry.name, BAS_MAX_CTRL_NAME, "%s", s->data);
|
||||
entry.menu = wmCreateMenu();
|
||||
arrput(menuForm->popupMenus, entry);
|
||||
menuForm->popupMenuCount = (int32_t)arrlen(menuForm->popupMenus);
|
||||
}
|
||||
|
||||
basStringUnref(s);
|
||||
return zeroValue();
|
||||
}
|
||||
|
||||
if (strcasecmp(methodName, "AddMenuItem") == 0) {
|
||||
// AddMenuItem parentMenu$, itemName$, caption$
|
||||
if (!menuForm || argc < 3) {
|
||||
return zeroValue();
|
||||
}
|
||||
|
||||
BasStringT *parent = basValFormatString(args[0]);
|
||||
BasStringT *itemN = basValFormatString(args[1]);
|
||||
BasStringT *capN = basValFormatString(args[2]);
|
||||
|
||||
BasFrmPopupMenuT *pm = findPopupMenu(menuForm, parent->data);
|
||||
|
||||
if (pm && pm->menu) {
|
||||
int32_t id = nextMenuItemId(menuForm);
|
||||
wmAddMenuItem(pm->menu, capN->data, id);
|
||||
|
||||
BasMenuIdMapT map;
|
||||
memset(&map, 0, sizeof(map));
|
||||
map.id = id;
|
||||
snprintf(map.name, BAS_MAX_CTRL_NAME, "%s", itemN->data);
|
||||
arrput(menuForm->menuIdMap, map);
|
||||
menuForm->menuIdMapCount = (int32_t)arrlen(menuForm->menuIdMap);
|
||||
|
||||
if (menuForm->window) {
|
||||
menuForm->window->onMenu = onFormMenu;
|
||||
}
|
||||
}
|
||||
|
||||
basStringUnref(parent);
|
||||
basStringUnref(itemN);
|
||||
basStringUnref(capN);
|
||||
return zeroValue();
|
||||
}
|
||||
|
||||
if (strcasecmp(methodName, "AddMenuSeparator") == 0) {
|
||||
// AddMenuSeparator parentMenu$
|
||||
if (!menuForm || argc < 1) {
|
||||
return zeroValue();
|
||||
}
|
||||
|
||||
BasStringT *parent = basValFormatString(args[0]);
|
||||
BasFrmPopupMenuT *pm = findPopupMenu(menuForm, parent->data);
|
||||
|
||||
if (pm && pm->menu) {
|
||||
wmAddMenuSeparator(pm->menu);
|
||||
}
|
||||
|
||||
basStringUnref(parent);
|
||||
return zeroValue();
|
||||
}
|
||||
|
||||
if (strcasecmp(methodName, "AddSubMenu") == 0) {
|
||||
// AddSubMenu parentMenu$, childName$, caption$
|
||||
// The new submenu can have AddMenuItem etc. called on it
|
||||
// via `childName`.
|
||||
if (!menuForm || argc < 3) {
|
||||
return zeroValue();
|
||||
}
|
||||
|
||||
BasStringT *parent = basValFormatString(args[0]);
|
||||
BasStringT *childN = basValFormatString(args[1]);
|
||||
BasStringT *capN = basValFormatString(args[2]);
|
||||
|
||||
BasFrmPopupMenuT *parentPm = findPopupMenu(menuForm, parent->data);
|
||||
|
||||
if (parentPm && parentPm->menu && !findPopupMenu(menuForm, childN->data)) {
|
||||
MenuT *sub = wmAddSubMenu(parentPm->menu, capN->data);
|
||||
|
||||
if (sub) {
|
||||
BasFrmPopupMenuT entry;
|
||||
memset(&entry, 0, sizeof(entry));
|
||||
snprintf(entry.name, BAS_MAX_CTRL_NAME, "%s", childN->data);
|
||||
// NB: sub is owned by parentPm->menu (wmFreeMenu
|
||||
// recurses); we store a non-owning reference here so
|
||||
// AddMenuItem etc. can look it up by name. Don't
|
||||
// wmFreeMenu the child on DestroyMenu for this
|
||||
// name; only the root popup owns the tree.
|
||||
entry.menu = sub;
|
||||
arrput(menuForm->popupMenus, entry);
|
||||
menuForm->popupMenuCount = (int32_t)arrlen(menuForm->popupMenus);
|
||||
}
|
||||
}
|
||||
|
||||
basStringUnref(parent);
|
||||
basStringUnref(childN);
|
||||
basStringUnref(capN);
|
||||
return zeroValue();
|
||||
}
|
||||
|
||||
// ========================================================
|
||||
// Toolbar runtime methods
|
||||
// ========================================================
|
||||
//
|
||||
// Toolbars hold buttons, separators, and other children laid
|
||||
// out horizontally. These convenience methods create the new
|
||||
// widget, register it on the form by name (so BASIC code can
|
||||
// do tbNew.Enabled = False afterwards), and tuck it into the
|
||||
// toolbar. Equivalent manual construction uses
|
||||
// CreateControl(form, "ImageButton", "tbNew", toolbar) -- these
|
||||
// just shorten the common case.
|
||||
|
||||
bool isToolbar = (ctrl && strcasecmp(ctrl->typeName, "Toolbar") == 0);
|
||||
|
||||
if (isToolbar && strcasecmp(methodName, "AddButton") == 0) {
|
||||
// AddButton name$, iconPath$
|
||||
//
|
||||
// Routed entirely through the generic runtime-creation +
|
||||
// property-set path so formrt doesn't link against the
|
||||
// ImageButton widget directly. CreateControl("ImageButton")
|
||||
// resolves the widget via wgtFindByBasName at call time, and
|
||||
// setProp "Picture" dispatches to whichever setter the
|
||||
// ImageButton DXE registered in its iface.
|
||||
if (!menuForm || !ctrl->widget || argc < 2) {
|
||||
return zeroValue();
|
||||
}
|
||||
|
||||
BasStringT *nameS = basValFormatString(args[0]);
|
||||
BasStringT *pathS = basValFormatString(args[1]);
|
||||
|
||||
BasControlT *btn = (BasControlT *)basFormRtCreateCtrlEx(sFormRt, menuForm, "ImageButton", nameS->data, ctrl);
|
||||
|
||||
if (btn) {
|
||||
BasValueT v = basValString(pathS);
|
||||
basFormRtSetProp(sFormRt, btn, "Picture", v);
|
||||
basValRelease(&v);
|
||||
}
|
||||
|
||||
basStringUnref(nameS);
|
||||
basStringUnref(pathS);
|
||||
return zeroValue();
|
||||
}
|
||||
|
||||
if (isToolbar && strcasecmp(methodName, "AddTextButton") == 0) {
|
||||
// AddTextButton name$, caption$
|
||||
if (!menuForm || !ctrl->widget || argc < 2) {
|
||||
return zeroValue();
|
||||
}
|
||||
|
||||
BasStringT *nameS = basValFormatString(args[0]);
|
||||
BasStringT *capS = basValFormatString(args[1]);
|
||||
|
||||
BasControlT *btn = (BasControlT *)basFormRtCreateCtrlEx(sFormRt, menuForm, "CommandButton", nameS->data, ctrl);
|
||||
|
||||
if (btn) {
|
||||
BasValueT v = basValString(capS);
|
||||
basFormRtSetProp(sFormRt, btn, "Caption", v);
|
||||
basValRelease(&v);
|
||||
}
|
||||
|
||||
basStringUnref(nameS);
|
||||
basStringUnref(capS);
|
||||
return zeroValue();
|
||||
}
|
||||
|
||||
if (isToolbar && strcasecmp(methodName, "AddSeparator") == 0) {
|
||||
// "Line" resolves to the separator widget via the iface
|
||||
// registry. The separator widget auto-orients to its parent
|
||||
// (vertical inside a horizontal container, horizontal inside
|
||||
// a vertical one), so we don't need a distinct VLine basName.
|
||||
if (menuForm && ctrl->widget) {
|
||||
basFormRtCreateCtrlEx(sFormRt, menuForm, "Line", "", ctrl);
|
||||
wgtInvalidate(ctrl->widget);
|
||||
}
|
||||
|
||||
return zeroValue();
|
||||
}
|
||||
|
||||
if (isToolbar && strcasecmp(methodName, "Clear") == 0) {
|
||||
// Destroy every child. Also remove their BasControlT
|
||||
// entries from the form so stale name lookups don't linger.
|
||||
if (!menuForm || !ctrl->widget) {
|
||||
return zeroValue();
|
||||
}
|
||||
|
||||
WidgetT *child = ctrl->widget->firstChild;
|
||||
|
||||
while (child) {
|
||||
WidgetT *next = child->nextSibling;
|
||||
|
||||
// Remove matching BasControlT from the form's control
|
||||
// list so its name becomes unknown again.
|
||||
for (int32_t i = 0; i < (int32_t)arrlen(menuForm->controls); i++) {
|
||||
if (menuForm->controls[i]->widget == child) {
|
||||
free(menuForm->controls[i]->tooltip);
|
||||
free(menuForm->controls[i]);
|
||||
arrdel(menuForm->controls, i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
wgtDestroy(child);
|
||||
child = next;
|
||||
}
|
||||
|
||||
wgtInvalidate(ctrl->widget);
|
||||
return zeroValue();
|
||||
}
|
||||
|
||||
if (isToolbar && strcasecmp(methodName, "ButtonCount") == 0) {
|
||||
int32_t count = 0;
|
||||
|
||||
if (ctrl->widget) {
|
||||
for (WidgetT *c = ctrl->widget->firstChild; c; c = c->nextSibling) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
return basValLong(count);
|
||||
}
|
||||
|
||||
if (strcasecmp(methodName, "DestroyMenu") == 0) {
|
||||
// DestroyMenu name$ -- free a top-level popup menu and
|
||||
// remove its entry. Submenus are freed recursively as
|
||||
// part of their owning parent; calling DestroyMenu on a
|
||||
// submenu name only removes the lookup entry.
|
||||
if (!menuForm || argc < 1) {
|
||||
return zeroValue();
|
||||
}
|
||||
|
||||
BasStringT *s = basValFormatString(args[0]);
|
||||
int32_t foundIdx = -1;
|
||||
|
||||
for (int32_t i = 0; i < menuForm->popupMenuCount; i++) {
|
||||
if (strcasecmp(menuForm->popupMenus[i].name, s->data) == 0) {
|
||||
foundIdx = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (foundIdx >= 0) {
|
||||
MenuT *m = menuForm->popupMenus[foundIdx].menu;
|
||||
|
||||
// Only free if this is a root (not a submenu whose
|
||||
// parent also has a pointer). Heuristic: check if any
|
||||
// other popup entry contains m as a descendant. Safe
|
||||
// alternative: if m appears elsewhere in the list, treat
|
||||
// the first occurrence as the root. For now we free
|
||||
// only if no other popup has the same MenuT*, which
|
||||
// handles the common case where a submenu is registered
|
||||
// under a separate name but still owned by its parent.
|
||||
bool isSubmenu = false;
|
||||
|
||||
for (int32_t i = 0; i < menuForm->popupMenuCount; i++) {
|
||||
if (i != foundIdx && menuForm->popupMenus[i].menu == m) {
|
||||
isSubmenu = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!isSubmenu) {
|
||||
wmFreeMenu(m);
|
||||
}
|
||||
|
||||
arrdel(menuForm->popupMenus, foundIdx);
|
||||
menuForm->popupMenuCount = (int32_t)arrlen(menuForm->popupMenus);
|
||||
}
|
||||
|
||||
basStringUnref(s);
|
||||
return zeroValue();
|
||||
}
|
||||
|
||||
// Unknown method: raise a loud runtime error (visible MessageBox +
|
||||
// DVX.LOG entry). Silent no-ops here used to hide typos in method
|
||||
// names. This is a runtime safety net -- the parser should reject
|
||||
|
|
@ -3108,6 +3665,12 @@ static void fireCtrlEvent(BasFormRtT *rt, BasControlT *ctrl, const char *eventNa
|
|||
static void frmLoad_onCtrlBegin(void *userData, const char *typeName, const char *name) {
|
||||
BasFrmLoadCtxT *ctx = (BasFrmLoadCtxT *)userData;
|
||||
|
||||
dvxLog("onCtrlBegin: type='%s' name='%s' form=%s nestDepth=%d",
|
||||
typeName ? typeName : "?",
|
||||
name ? name : "?",
|
||||
(ctx->form && ctx->form->name[0]) ? ctx->form->name : "(null)",
|
||||
(int)ctx->nestDepth);
|
||||
|
||||
if (!ctx->form || ctx->nestDepth <= 0) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -3316,6 +3879,7 @@ static void frmLoad_onMenuBegin(void *userData, const char *name, int32_t level)
|
|||
snprintf(mi.name, BAS_MAX_CTRL_NAME, "%s", name);
|
||||
mi.level = level;
|
||||
mi.enabled = true;
|
||||
mi.visible = true;
|
||||
arrput(ctx->menuItems, mi);
|
||||
ctx->curMenuItemIdx = (int32_t)arrlen(ctx->menuItems) - 1;
|
||||
ctx->current = NULL;
|
||||
|
|
@ -3352,6 +3916,10 @@ static void frmLoad_onMenuProp(void *userData, const char *key, const char *valu
|
|||
} else if (strcasecmp(key, "Enabled") == 0) {
|
||||
// Default-true: anything not "False" enables the item.
|
||||
mip->enabled = (strcasecmp(text, "False") != 0);
|
||||
} else if (strcasecmp(key, "Visible") == 0) {
|
||||
// Top-level only: Visible=False marks the menu as a popup
|
||||
// (not part of the menu bar). Ignored on nested items.
|
||||
mip->visible = (strcasecmp(text, "False") != 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -4618,6 +5186,31 @@ static bool setCommonProp(BasControlT *ctrl, const char *propName, BasValueT val
|
|||
return true;
|
||||
}
|
||||
|
||||
if (strcasecmp(propName, "ContextMenu") == 0) {
|
||||
// Set the auto-right-click menu. Empty string clears it.
|
||||
BasStringT *s = basValFormatString(value);
|
||||
MenuT *m = NULL;
|
||||
|
||||
if (s->len > 0 && ctrl->form) {
|
||||
BasFrmPopupMenuT *pm = findPopupMenu(ctrl->form, s->data);
|
||||
|
||||
if (pm) {
|
||||
m = pm->menu;
|
||||
}
|
||||
}
|
||||
|
||||
if (ctrl->widget) {
|
||||
ctrl->widget->contextMenu = m;
|
||||
} else if (ctrl->form && ctrl->form->window && ctrl == &ctrl->form->formCtrl) {
|
||||
// Form-level ContextMenu: attach to the window so clicks
|
||||
// outside any child widget still pop up the menu.
|
||||
ctrl->form->window->contextMenu = m;
|
||||
}
|
||||
|
||||
basStringUnref(s);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -62,6 +62,12 @@ typedef struct {
|
|||
BasControlT *proxy; // heap-allocated proxy for property access (widget=NULL, menuId stored)
|
||||
} BasMenuIdMapT;
|
||||
|
||||
// Named popup / context menu owned by a form.
|
||||
typedef struct {
|
||||
char name[BAS_MAX_CTRL_NAME];
|
||||
MenuT *menu; // standalone wmCreateMenu allocation, owned
|
||||
} BasFrmPopupMenuT;
|
||||
|
||||
// ============================================================
|
||||
// Control instance (a widget on a form)
|
||||
// ============================================================
|
||||
|
|
@ -129,6 +135,13 @@ typedef struct BasFormT {
|
|||
// Menu ID to name mapping (for event dispatch)
|
||||
BasMenuIdMapT *menuIdMap;
|
||||
int32_t menuIdMapCount;
|
||||
// Named popup (context) menus. Allocated standalone via
|
||||
// wmCreateMenu() either from .frm `Begin Menu` with Visible=False
|
||||
// or at runtime via CreateMenu/AddMenuItem BASIC calls. Click
|
||||
// dispatch reuses menuIdMap above so Foo_Click-style handlers
|
||||
// work the same as for menu-bar items. Freed on form unload.
|
||||
BasFrmPopupMenuT *popupMenus; // stb_ds array
|
||||
int32_t popupMenuCount;
|
||||
// Synthetic control entry for the form itself, so that
|
||||
// FormName.Property works through the same getProp/setProp path.
|
||||
BasControlT formCtrl;
|
||||
|
|
@ -167,6 +180,21 @@ typedef struct {
|
|||
// loop exits at the next pump so the app doesn't stumble forward
|
||||
// with a halted VM.
|
||||
bool terminated;
|
||||
// Set true by hosts that want to handle runtime-error reporting
|
||||
// themselves (the IDE shows the error in the output pane and
|
||||
// navigates the editor to the line). When true,
|
||||
// basFormRtRuntimeError still logs and halts but skips the modal
|
||||
// MessageBox -- this removes a stray second OK press the IDE users
|
||||
// were seeing when the dialog's event pump raced with other
|
||||
// queued events.
|
||||
bool suppressErrorDialog;
|
||||
// Name of the most recent basFormRtFindCtrl lookup. The VM's
|
||||
// OP_FIND_CTRL opcode discards the name string after the lookup,
|
||||
// so when a subsequent OP_CALL_METHOD / OP_SET_PROP sees a NULL
|
||||
// ctrl we'd otherwise have no way to tell the user WHICH control
|
||||
// was missing. This is the last requested name; if the lookup
|
||||
// succeeded this value is still the name of that control.
|
||||
char lastLookupName[BAS_MAX_CTRL_NAME];
|
||||
} BasFormRtT;
|
||||
|
||||
// ============================================================
|
||||
|
|
|
|||
|
|
@ -616,6 +616,7 @@ static void dsgnLoad_onMenuBegin(void *userData, const char *name, int32_t level
|
|||
snprintf(mi.name, DSGN_MAX_NAME, "%s", name);
|
||||
mi.level = level;
|
||||
mi.enabled = true;
|
||||
mi.visible = true;
|
||||
arrput(ctx->form->menuItems, mi);
|
||||
ctx->curMenuItemIdx = (int32_t)arrlen(ctx->form->menuItems) - 1;
|
||||
ctx->current = NULL;
|
||||
|
|
@ -653,6 +654,8 @@ static void dsgnLoad_onMenuProp(void *userData, const char *key, const char *val
|
|||
mip->radioCheck = frmParseBool(val);
|
||||
} else if (strcasecmp(key, "Enabled") == 0) {
|
||||
mip->enabled = frmParseBool(val);
|
||||
} else if (strcasecmp(key, "Visible") == 0) {
|
||||
mip->visible = (strcasecmp(val, "False") != 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1038,6 +1041,17 @@ int32_t dsgnSaveFrm(const DsgnStateT *ds, char *buf, int32_t bufSize) {
|
|||
pos += snprintf(buf + pos, bufSize - pos, "Enabled = False\n");
|
||||
}
|
||||
|
||||
// Visible is only meaningful on top-level menus -- False
|
||||
// marks it as a popup (not on the menu bar). Never
|
||||
// serialize the default True.
|
||||
if (mi->level == 0 && !mi->visible) {
|
||||
for (int32_t p = 0; p < (mi->level + 2) * 4; p++) {
|
||||
buf[pos++] = ' ';
|
||||
}
|
||||
|
||||
pos += snprintf(buf + pos, bufSize - pos, "Visible = False\n");
|
||||
}
|
||||
|
||||
curLevel = mi->level + 1;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -70,6 +70,7 @@ typedef struct {
|
|||
bool checked;
|
||||
bool radioCheck; // true = radio bullet instead of checkmark
|
||||
bool enabled; // default true
|
||||
bool visible; // default true; false on a top-level item = popup-only (not on menu bar)
|
||||
} DsgnMenuItemT;
|
||||
|
||||
// ============================================================
|
||||
|
|
|
|||
|
|
@ -603,6 +603,11 @@ static int32_t sOutputLen = 0;
|
|||
// swaps directly between buffers with no splicing needed.
|
||||
static char *sGeneralBuf = NULL; // (General) section: module-level code
|
||||
static char **sProcBufs = NULL; // stb_ds array: one buffer per procedure
|
||||
// Cached copy of the source last passed to parseProcs (a .bas file's
|
||||
// contents, or a .frm's extracted code section). updateDropdowns
|
||||
// scans this so sProcTable.lineNum is in the same line-number space
|
||||
// prjMapLine returns -- getFullSource() packs blank lines and diverges.
|
||||
static char *sParsedSource = NULL;
|
||||
static int32_t sCurProcIdx = -2; // which buffer is in the editor (-1=General, -2=none)
|
||||
static int32_t sEditorFileIdx = -1; // which project file owns sProcBufs (-1=none)
|
||||
static int32_t sEditorLineCount = 0; // line count for breakpoint adjustment on edit
|
||||
|
|
@ -625,7 +630,8 @@ static WidgetT *sDirGroup = NULL; // radio group: 0=Fwd, 1=Back
|
|||
typedef struct {
|
||||
char objName[64];
|
||||
char evtName[64];
|
||||
int32_t lineNum;
|
||||
int32_t lineNum; // line of the SUB / FUNCTION declaration
|
||||
int32_t endLineNum; // line of the END SUB / END FUNCTION
|
||||
} IdeProcEntryT;
|
||||
|
||||
static IdeProcEntryT *sProcTable = NULL; // stb_ds dynamic array
|
||||
|
|
@ -1533,7 +1539,18 @@ static bool ideValidator_isCommonMethod(const char *methodName) {
|
|||
strcasecmp(methodName, "Refresh") == 0 ||
|
||||
strcasecmp(methodName, "SetReadOnly") == 0 ||
|
||||
strcasecmp(methodName, "SetEnabled") == 0 ||
|
||||
strcasecmp(methodName, "SetVisible") == 0;
|
||||
strcasecmp(methodName, "SetVisible") == 0 ||
|
||||
strcasecmp(methodName, "PopupMenu") == 0 ||
|
||||
strcasecmp(methodName, "CreateMenu") == 0 ||
|
||||
strcasecmp(methodName, "AddMenuItem") == 0 ||
|
||||
strcasecmp(methodName, "AddMenuSeparator") == 0 ||
|
||||
strcasecmp(methodName, "AddSubMenu") == 0 ||
|
||||
strcasecmp(methodName, "DestroyMenu") == 0 ||
|
||||
strcasecmp(methodName, "AddButton") == 0 ||
|
||||
strcasecmp(methodName, "AddTextButton") == 0 ||
|
||||
strcasecmp(methodName, "AddSeparator") == 0 ||
|
||||
strcasecmp(methodName, "Clear") == 0 ||
|
||||
strcasecmp(methodName, "ButtonCount") == 0;
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -1560,6 +1577,7 @@ static bool ideValidator_isCommonProp(const char *propName) {
|
|||
strcasecmp(propName, "Text") == 0 ||
|
||||
strcasecmp(propName, "HelpTopic") == 0 ||
|
||||
strcasecmp(propName, "ToolTipText") == 0 ||
|
||||
strcasecmp(propName, "ContextMenu") == 0 ||
|
||||
strcasecmp(propName, "DataSource") == 0 ||
|
||||
strcasecmp(propName, "DataField") == 0 ||
|
||||
strcasecmp(propName, "ListCount") == 0;
|
||||
|
|
@ -1630,6 +1648,7 @@ static bool ideValidator_isPropValid(void *ctx, const char *wgtType, const char
|
|||
strcasecmp(propName, "Resizable") == 0 ||
|
||||
strcasecmp(propName, "AutoSize") == 0 ||
|
||||
strcasecmp(propName, "Centered") == 0 ||
|
||||
strcasecmp(propName, "ContextMenu") == 0 ||
|
||||
strcasecmp(propName, "Layout") == 0;
|
||||
}
|
||||
|
||||
|
|
@ -1638,6 +1657,8 @@ static bool ideValidator_isPropValid(void *ctx, const char *wgtType, const char
|
|||
return strcasecmp(propName, "Name") == 0 ||
|
||||
strcasecmp(propName, "Checked") == 0 ||
|
||||
strcasecmp(propName, "Enabled") == 0 ||
|
||||
strcasecmp(propName, "Visible") == 0 || // top-level: False = popup
|
||||
strcasecmp(propName, "RadioCheck") == 0 ||
|
||||
strcasecmp(propName, "Caption") == 0;
|
||||
}
|
||||
|
||||
|
|
@ -2135,30 +2156,36 @@ static void debugNavigateToLine(int32_t concatLine) {
|
|||
int32_t fileIdx = -1;
|
||||
int32_t localLine = concatLine;
|
||||
|
||||
// Map concatenated line to file and file-local line (code section)
|
||||
// Map concatenated line to file and file-local line (code section).
|
||||
// prjMapLine already returns a line relative to the code section
|
||||
// (startLine in the source map is recorded AFTER the injected
|
||||
// BEGINFORM directive), so no further adjustment is needed. The
|
||||
// compile-error path uses the result as-is and this path should too.
|
||||
if (sProject.sourceMapCount > 0) {
|
||||
prjMapLine(&sProject, concatLine, &fileIdx, &localLine);
|
||||
|
||||
// For .frm files, subtract the injected BEGINFORM line
|
||||
if (fileIdx >= 0 && fileIdx < sProject.fileCount &&
|
||||
sProject.files[fileIdx].isForm) {
|
||||
localLine--;
|
||||
}
|
||||
}
|
||||
|
||||
// Find which procedure we're in using the VM's PC and the compiled
|
||||
// module's proc table, then build a dot-separated name for navigateToCodeLine.
|
||||
// When a runtime error has halted the program, vm->pc has already
|
||||
// been restored by runSubLoop to the caller's saved PC (outside the
|
||||
// event handler that actually faulted), so use the snapshot
|
||||
// captured in vm->errorPc instead. Without this, the proc lookup
|
||||
// lands on whatever sub owns the saved PC -- typically "(General)"
|
||||
// or the wrong handler -- and the editor jumps to the wrong place.
|
||||
const char *procName = NULL;
|
||||
char procBuf[128];
|
||||
|
||||
if (sVm && sDbgModule) {
|
||||
const char *compiledName = NULL;
|
||||
int32_t bestAddr = -1;
|
||||
int32_t lookupPc = (sVm->errorMsg[0] && sVm->errorPc > 0)
|
||||
? sVm->errorPc : sVm->pc;
|
||||
|
||||
for (int32_t i = 0; i < sDbgModule->procCount; i++) {
|
||||
int32_t addr = sDbgModule->procs[i].codeAddr;
|
||||
|
||||
if (addr <= sVm->pc && addr > bestAddr) {
|
||||
if (addr <= lookupPc && addr > bestAddr) {
|
||||
bestAddr = addr;
|
||||
compiledName = sDbgModule->procs[i].name;
|
||||
}
|
||||
|
|
@ -3313,6 +3340,9 @@ static void freeProcBufs(void) {
|
|||
free(sGeneralBuf);
|
||||
sGeneralBuf = NULL;
|
||||
|
||||
free(sParsedSource);
|
||||
sParsedSource = NULL;
|
||||
|
||||
for (int32_t i = 0; i < (int32_t)arrlen(sProcBufs); i++) {
|
||||
free(sProcBufs[i]);
|
||||
}
|
||||
|
|
@ -3747,6 +3777,8 @@ static void handleRunCmd(int32_t cmd) {
|
|||
case CMD_OUTPUT_TO_LOG:
|
||||
if (sWin && sWin->menuBar) {
|
||||
sOutputToLog = wmMenuItemIsChecked(sWin->menuBar, CMD_OUTPUT_TO_LOG);
|
||||
prefsSetBool(sPrefs, "run", "outputToLog", sOutputToLog);
|
||||
prefsSave(sPrefs);
|
||||
}
|
||||
break;
|
||||
|
||||
|
|
@ -3914,10 +3946,9 @@ static void handleWindowCmd(int32_t cmd) {
|
|||
|
||||
case CMD_WIN_TOOLBOX:
|
||||
if (!sToolboxWin) {
|
||||
sToolboxWin = tbxCreate(sAc, &sDesigner);
|
||||
sToolboxWin = tbxCreate(sAc, &sDesigner, toolbarBottom());
|
||||
|
||||
if (sToolboxWin) {
|
||||
sToolboxWin->y = toolbarBottom();
|
||||
sToolboxWin->onMenu = onMenu;
|
||||
sToolboxWin->accelTable = sWin ? sWin->accelTable : NULL;
|
||||
}
|
||||
|
|
@ -4759,7 +4790,11 @@ static void loadFormCodeIntoEditor(void) {
|
|||
// form runtime for execution.
|
||||
|
||||
static void loadFrmFiles(BasFormRtT *rt) {
|
||||
dvxLog("loadFrmFiles: fileCount=%d", (int)sProject.fileCount);
|
||||
|
||||
for (int32_t i = 0; i < sProject.fileCount; i++) {
|
||||
dvxLog(" file[%d] path=%s isForm=%d", (int)i, sProject.files[i].path, (int)sProject.files[i].isForm);
|
||||
|
||||
if (!sProject.files[i].isForm) {
|
||||
continue;
|
||||
}
|
||||
|
|
@ -4770,6 +4805,8 @@ static void loadFrmFiles(BasFormRtT *rt) {
|
|||
int32_t bytesRead = 0;
|
||||
char *frmBuf = platformReadFile(fullPath, &bytesRead);
|
||||
|
||||
dvxLog(" loadFrmFiles: fullPath=%s bytes=%d frmBuf=%s", fullPath, (int)bytesRead, frmBuf ? "ok" : "null");
|
||||
|
||||
if (!frmBuf) {
|
||||
continue;
|
||||
}
|
||||
|
|
@ -4782,6 +4819,9 @@ static void loadFrmFiles(BasFormRtT *rt) {
|
|||
BasFormT *form = basFormRtLoadFrm(rt, frmBuf, bytesRead);
|
||||
free(frmBuf);
|
||||
|
||||
dvxLog(" loadFrmFiles: basFormRtLoadFrm returned form='%s'",
|
||||
form && form->name[0] ? form->name : "(null)");
|
||||
|
||||
// Cache the form object name in the project file entry
|
||||
if (form && form->name[0]) {
|
||||
snprintf(sProject.files[i].formName, sizeof(sProject.files[i].formName), "%s", form->name);
|
||||
|
|
@ -7035,6 +7075,9 @@ static void openProject(void) {
|
|||
static void parseProcs(const char *source) {
|
||||
freeProcBufs();
|
||||
|
||||
free(sParsedSource);
|
||||
sParsedSource = source ? strdup(source) : NULL;
|
||||
|
||||
if (!source) {
|
||||
sGeneralBuf = strdup("");
|
||||
return;
|
||||
|
|
@ -7597,6 +7640,16 @@ static void runModule(BasModuleT *mod) {
|
|||
// Create form runtime (bridges UI opcodes to DVX widgets)
|
||||
BasFormRtT *formRt = basFormRtCreate(sAc, vm, mod);
|
||||
|
||||
// The IDE surfaces runtime errors via the Output pane and jumps
|
||||
// the editor to the faulting line, so suppress the form runtime's
|
||||
// modal error dialog. Keeping the dialog on top of an already-
|
||||
// modal-heavy IDE session was producing phantom "press OK twice"
|
||||
// UX as the dialog's nested event pump raced with other queued
|
||||
// events coming out of the user's program.
|
||||
if (formRt) {
|
||||
formRt->suppressErrorDialog = true;
|
||||
}
|
||||
|
||||
sVm = vm;
|
||||
sDbgFormRt = formRt;
|
||||
sDbgModule = mod;
|
||||
|
|
@ -7673,18 +7726,18 @@ static void runModule(BasModuleT *mod) {
|
|||
break;
|
||||
}
|
||||
|
||||
// Runtime error — navigate to error line
|
||||
int32_t pos = sOutputLen;
|
||||
int32_t n = snprintf(sOutputBuf + pos, IDE_MAX_OUTPUT - pos, "\n[Runtime error: %s]\n", basVmGetError(vm));
|
||||
sOutputLen += n;
|
||||
setOutputText(sOutputBuf);
|
||||
|
||||
if (vm->currentLine > 0 && sEditor) {
|
||||
wgtTextAreaGoToLine(sEditor, vm->currentLine);
|
||||
|
||||
if (sCodeWin && !sCodeWin->visible) {
|
||||
dvxShowWindow(sAc, sCodeWin);
|
||||
// Module-level runtime error. For VM-internal errors (e.g.
|
||||
// division by zero) there's no vm->errorLine set, so fall
|
||||
// back to vm->currentLine -- but copy it into errorLine so
|
||||
// the shared post-loop handler below can format and navigate
|
||||
// uniformly with the event-handler error path.
|
||||
if (vm->errorMsg[0] == '\0') {
|
||||
const char *msg = basVmGetError(vm);
|
||||
snprintf(vm->errorMsg, sizeof(vm->errorMsg), "%s", msg ? msg : "Runtime error");
|
||||
}
|
||||
|
||||
if (vm->errorLine == 0 && vm->currentLine > 0) {
|
||||
vm->errorLine = vm->currentLine;
|
||||
}
|
||||
|
||||
break;
|
||||
|
|
@ -7720,6 +7773,84 @@ static void runModule(BasModuleT *mod) {
|
|||
}
|
||||
}
|
||||
|
||||
// A runtime error fired from a host callback (e.g. setting a
|
||||
// property on an unknown control) halts execution by writing
|
||||
// vm->errorMsg and setting vm->ended. basVmRun surfaces that as
|
||||
// BAS_VM_ERROR for module-level code, but errors raised inside an
|
||||
// event handler just break the loop above. Either way, report
|
||||
// the message and navigate the editor to the offending line so
|
||||
// the user can fix it. debugNavigateToLine depends on sVm and
|
||||
// sDbgModule, so it has to run BEFORE the cleanup block below.
|
||||
// Use vm->errorLine (snapshot at error time) rather than
|
||||
// vm->currentLine: the dialog/event pump can fire another handler
|
||||
// whose OP_LINE overwrites currentLine before we read it here.
|
||||
bool hadRuntimeError = (vm->errorMsg[0] != '\0') && sWin;
|
||||
int32_t errorLine = hadRuntimeError ? vm->errorLine : 0;
|
||||
|
||||
if (hadRuntimeError) {
|
||||
// Map the concatenated line to a file / proc-local line the
|
||||
// same way compile errors do, so the Output message matches
|
||||
// what the editor is actually showing.
|
||||
int32_t errFileIdx = -1;
|
||||
int32_t errLocalLine = errorLine;
|
||||
const char *errFile = "";
|
||||
|
||||
if (errorLine > 0 && sProject.fileCount > 0 && sProject.sourceMapCount > 0) {
|
||||
prjMapLine(&sProject, errorLine, &errFileIdx, &errLocalLine);
|
||||
|
||||
if (errFileIdx >= 0 && errFileIdx < sProject.fileCount) {
|
||||
errFile = sProject.files[errFileIdx].path;
|
||||
}
|
||||
}
|
||||
|
||||
// Activate the file (populates sProcTable for that file), then
|
||||
// find the proc whose body actually contains errLocalLine.
|
||||
// A simple "last proc with lineNum <= errLocalLine" match is
|
||||
// wrong when the error is module-level code between procs:
|
||||
// it lands on whichever proc just finished instead of
|
||||
// correctly reporting that the error is outside any sub.
|
||||
// Use the proc's endLineNum to gate membership.
|
||||
char procName[128] = {0};
|
||||
int32_t procLine = errLocalLine;
|
||||
|
||||
if (errFileIdx >= 0) {
|
||||
activateFile(errFileIdx, ViewCodeE);
|
||||
|
||||
int32_t procCount = (int32_t)arrlen(sProcTable);
|
||||
|
||||
for (int32_t i = 0; i < procCount; i++) {
|
||||
if (errLocalLine >= sProcTable[i].lineNum &&
|
||||
errLocalLine <= sProcTable[i].endLineNum) {
|
||||
snprintf(procName, sizeof(procName), "%s.%s",
|
||||
sProcTable[i].objName, sProcTable[i].evtName);
|
||||
procLine = errLocalLine - sProcTable[i].lineNum + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
navigateToCodeLine(errFileIdx, errLocalLine, procName[0] ? procName : NULL, false);
|
||||
}
|
||||
|
||||
int32_t pos = sOutputLen;
|
||||
int32_t n;
|
||||
|
||||
if (procName[0]) {
|
||||
n = snprintf(sOutputBuf + pos, IDE_MAX_OUTPUT - pos,
|
||||
"\nRUNTIME ERROR:\n%s line %d: %s\n",
|
||||
procName, (int)procLine, vm->errorMsg);
|
||||
} else if (errFile[0]) {
|
||||
n = snprintf(sOutputBuf + pos, IDE_MAX_OUTPUT - pos,
|
||||
"\nRUNTIME ERROR:\n%s line %d: %s\n",
|
||||
errFile, (int)errLocalLine, vm->errorMsg);
|
||||
} else {
|
||||
n = snprintf(sOutputBuf + pos, IDE_MAX_OUTPUT - pos,
|
||||
"\nRUNTIME ERROR:\nline %d: %s\n",
|
||||
(int)errLocalLine, vm->errorMsg);
|
||||
}
|
||||
|
||||
sOutputLen += n;
|
||||
}
|
||||
|
||||
sVm = NULL;
|
||||
sDbgFormRt = NULL;
|
||||
sDbgModule = NULL;
|
||||
|
|
@ -7739,14 +7870,28 @@ static void runModule(BasModuleT *mod) {
|
|||
updateProjectMenuState();
|
||||
setOutputText(sOutputBuf);
|
||||
|
||||
setStatus("Done.");
|
||||
setStatus(hadRuntimeError ? "Runtime error." : "Done.");
|
||||
|
||||
// Restore IDE windows
|
||||
// Restore IDE windows. On a runtime error we want the code
|
||||
// window on top so the user lands on the offending line; don't
|
||||
// pop the form designer / toolbox / project back up, since that
|
||||
// would cover the editor (dvxShowWindow raises). Output window
|
||||
// stays up too so the error text is visible.
|
||||
if (hadRuntimeError) {
|
||||
if (sCodeWin && !sCodeWin->visible) {
|
||||
dvxShowWindow(sAc, sCodeWin);
|
||||
}
|
||||
|
||||
if (sCodeWin) {
|
||||
dvxRaiseWindow(sAc, sCodeWin);
|
||||
}
|
||||
} else {
|
||||
if (hadFormWin && sFormWin) { dvxShowWindow(sAc, sFormWin); }
|
||||
if (hadToolbox && sToolboxWin) { dvxShowWindow(sAc, sToolboxWin); }
|
||||
if (hadProps && sPropsWin) { dvxShowWindow(sAc, sPropsWin); }
|
||||
if (hadCodeWin && sCodeWin) { dvxShowWindow(sAc, sCodeWin); }
|
||||
if (hadPrjWin && sProjectWin) { dvxShowWindow(sAc, sProjectWin); }
|
||||
}
|
||||
|
||||
// Repaint to clear destroyed runtime forms and restore designer
|
||||
dvxUpdate(sAc);
|
||||
|
|
@ -8863,10 +9008,9 @@ static void switchToDesign(void) {
|
|||
|
||||
// Create toolbox and properties windows
|
||||
if (!sToolboxWin) {
|
||||
sToolboxWin = tbxCreate(sAc, &sDesigner);
|
||||
sToolboxWin = tbxCreate(sAc, &sDesigner, toolbarBottom());
|
||||
|
||||
if (sToolboxWin) {
|
||||
sToolboxWin->y = toolbarBottom();
|
||||
sToolboxWin->onMenu = onMenu;
|
||||
sToolboxWin->accelTable = sWin ? sWin->accelTable : NULL;
|
||||
}
|
||||
|
|
@ -9273,8 +9417,16 @@ static void updateDropdowns(void) {
|
|||
return;
|
||||
}
|
||||
|
||||
// Scan the reassembled full source
|
||||
const char *src = getFullSource();
|
||||
// Scan the ORIGINAL parsed source (a .bas file or a .frm's code
|
||||
// section) so line numbers match what prjMapLine returns.
|
||||
// getFullSource() packs blank lines between procs, so its line
|
||||
// numbering diverges from the file and breaks runtime error
|
||||
// navigation and the navigateToCodeLine line translation.
|
||||
const char *src = sParsedSource;
|
||||
|
||||
if (!src) {
|
||||
src = getFullSource();
|
||||
}
|
||||
|
||||
if (!src) {
|
||||
return;
|
||||
|
|
@ -9306,15 +9458,19 @@ static void updateDropdowns(void) {
|
|||
|
||||
procName[nameLen] = '\0';
|
||||
|
||||
// Find End Sub / End Function
|
||||
// Find End Sub / End Function and record its line.
|
||||
const char *endTag = isSub ? "END SUB" : "END FUNCTION";
|
||||
int32_t endTagLen = isSub ? 7 : 12;
|
||||
const char *scan = pos;
|
||||
int32_t scanLine = lineNum; // line of the Sub/Function declaration
|
||||
int32_t endLineNum = 0;
|
||||
|
||||
while (*scan) {
|
||||
const char *sl = dvxSkipWs(scan);
|
||||
|
||||
if (strncasecmp(sl, endTag, endTagLen) == 0) {
|
||||
endLineNum = scanLine;
|
||||
|
||||
// Advance past the End line
|
||||
while (*scan && *scan != '\n') {
|
||||
scan++;
|
||||
|
|
@ -9322,6 +9478,7 @@ static void updateDropdowns(void) {
|
|||
|
||||
if (*scan == '\n') {
|
||||
scan++;
|
||||
scanLine++;
|
||||
}
|
||||
|
||||
break;
|
||||
|
|
@ -9333,12 +9490,14 @@ static void updateDropdowns(void) {
|
|||
|
||||
if (*scan == '\n') {
|
||||
scan++;
|
||||
scanLine++;
|
||||
}
|
||||
}
|
||||
|
||||
IdeProcEntryT entry;
|
||||
memset(&entry, 0, sizeof(entry));
|
||||
entry.lineNum = lineNum;
|
||||
entry.endLineNum = endLineNum > 0 ? endLineNum : scanLine;
|
||||
|
||||
// Match proc name against known objects: form name,
|
||||
// controls, menu items. Try each as a prefix followed
|
||||
|
|
@ -9813,6 +9972,9 @@ int32_t appMain(DxeAppContextT *ctx) {
|
|||
if (sWin && sWin->menuBar) {
|
||||
bool saveOnRun = prefsGetBool(sPrefs, "run", "saveOnRun", true);
|
||||
wmMenuItemSetChecked(sWin->menuBar, CMD_SAVE_ON_RUN, saveOnRun);
|
||||
|
||||
sOutputToLog = prefsGetBool(sPrefs, "run", "outputToLog", false);
|
||||
wmMenuItemSetChecked(sWin->menuBar, CMD_OUTPUT_TO_LOG, sOutputToLog);
|
||||
}
|
||||
|
||||
// Load recent files list and populate menu
|
||||
|
|
|
|||
|
|
@ -70,6 +70,7 @@ typedef struct {
|
|||
WidgetT *checkedCb;
|
||||
WidgetT *radioCheckCb;
|
||||
WidgetT *enabledCb;
|
||||
WidgetT *popupCb; // Top-level popup (hidden from menu bar)
|
||||
WidgetT *listBox;
|
||||
} MnuEdStateT;
|
||||
|
||||
|
|
@ -169,6 +170,13 @@ static void applyFields(void) {
|
|||
mi->checked = wgtCheckboxIsChecked(sMed.checkedCb);
|
||||
mi->radioCheck = wgtCheckboxIsChecked(sMed.radioCheckCb);
|
||||
mi->enabled = wgtCheckboxIsChecked(sMed.enabledCb);
|
||||
// Popup checkbox only meaningful on top-level items; for nested
|
||||
// items `visible` stays true (the field is ignored there).
|
||||
if (mi->level == 0 && sMed.popupCb) {
|
||||
mi->visible = !wgtCheckboxIsChecked(sMed.popupCb);
|
||||
} else {
|
||||
mi->visible = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -198,6 +206,10 @@ static void loadFields(void) {
|
|||
wgtCheckboxSetChecked(sMed.checkedCb, false);
|
||||
wgtCheckboxSetChecked(sMed.radioCheckCb, false);
|
||||
wgtCheckboxSetChecked(sMed.enabledCb, true);
|
||||
if (sMed.popupCb) {
|
||||
wgtCheckboxSetChecked(sMed.popupCb, false);
|
||||
wgtSetEnabled(sMed.popupCb, false);
|
||||
}
|
||||
sMed.nameAutoGen = true; // new blank item -- auto-gen eligible
|
||||
return;
|
||||
}
|
||||
|
|
@ -209,6 +221,11 @@ static void loadFields(void) {
|
|||
wgtCheckboxSetChecked(sMed.checkedCb, mi->checked);
|
||||
wgtCheckboxSetChecked(sMed.radioCheckCb, mi->radioCheck);
|
||||
wgtCheckboxSetChecked(sMed.enabledCb, mi->enabled);
|
||||
if (sMed.popupCb) {
|
||||
// Popup (Visible=False) only applies to top-level menus.
|
||||
wgtCheckboxSetChecked(sMed.popupCb, mi->level == 0 && !mi->visible);
|
||||
wgtSetEnabled(sMed.popupCb, mi->level == 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -230,6 +247,7 @@ bool mnuEditorDialog(AppContextT *ctx, DsgnFormT *form) {
|
|||
DsgnMenuItemT mi;
|
||||
memset(&mi, 0, sizeof(mi));
|
||||
mi.enabled = true;
|
||||
mi.visible = true;
|
||||
arrput(sMed.items, mi);
|
||||
}
|
||||
|
||||
|
|
@ -271,6 +289,7 @@ bool mnuEditorDialog(AppContextT *ctx, DsgnFormT *form) {
|
|||
sMed.checkedCb = wgtCheckbox(chkRow, "Checked");
|
||||
sMed.radioCheckCb = wgtCheckbox(chkRow, "RadioCheck");
|
||||
sMed.enabledCb = wgtCheckbox(chkRow, "Enabled");
|
||||
sMed.popupCb = wgtCheckbox(chkRow, "Popup");
|
||||
wgtCheckboxSetChecked(sMed.enabledCb, true);
|
||||
|
||||
// Listbox
|
||||
|
|
@ -444,6 +463,7 @@ static void onInsert(WidgetT *w) {
|
|||
DsgnMenuItemT mi;
|
||||
memset(&mi, 0, sizeof(mi));
|
||||
mi.enabled = true;
|
||||
mi.visible = true;
|
||||
|
||||
// Insert after the current item's subtree, at the same level
|
||||
int32_t insertAt;
|
||||
|
|
@ -583,6 +603,7 @@ static void onNext(WidgetT *w) {
|
|||
DsgnMenuItemT mi;
|
||||
memset(&mi, 0, sizeof(mi));
|
||||
mi.enabled = true;
|
||||
mi.visible = true;
|
||||
|
||||
if (count > 0) {
|
||||
mi.level = sMed.items[count - 1].level;
|
||||
|
|
|
|||
|
|
@ -95,11 +95,11 @@ static void onToolClick(WidgetT *w) {
|
|||
}
|
||||
|
||||
|
||||
WindowT *tbxCreate(AppContextT *ctx, DsgnStateT *ds) {
|
||||
WindowT *tbxCreate(AppContextT *ctx, DsgnStateT *ds, int32_t y) {
|
||||
sDs = ds;
|
||||
arrsetlen(sTbxTools, 0);
|
||||
|
||||
WindowT *win = dvxCreateWindow(ctx, "Toolbox", 0, 30, TBX_WIN_W, TBX_WIN_H, false);
|
||||
WindowT *win = dvxCreateWindow(ctx, "Toolbox", 0, y, TBX_WIN_W, TBX_WIN_H, false);
|
||||
|
||||
if (!win) {
|
||||
return NULL;
|
||||
|
|
|
|||
|
|
@ -27,8 +27,12 @@
|
|||
|
||||
#include "ideDesigner.h"
|
||||
|
||||
// Create the toolbox floating window. Returns the WindowT pointer.
|
||||
WindowT *tbxCreate(AppContextT *ctx, DsgnStateT *ds);
|
||||
// Create the toolbox floating window at the given y coordinate.
|
||||
// Caller owns y so the window is painted at its final location
|
||||
// during the asynchronous icon-loading dvxUpdate cycles; moving
|
||||
// the window after creation would leave stale paint at the old
|
||||
// position on the backbuffer. Returns the WindowT pointer.
|
||||
WindowT *tbxCreate(AppContextT *ctx, DsgnStateT *ds, int32_t y);
|
||||
|
||||
// Destroy the toolbox window.
|
||||
void tbxDestroy(AppContextT *ctx, WindowT *win);
|
||||
|
|
|
|||
|
|
@ -327,6 +327,8 @@ void basVmReset(BasVmT *vm) {
|
|||
vm->errorNumber = 0;
|
||||
vm->errorPc = 0;
|
||||
vm->errorNextPc = 0;
|
||||
vm->errorLine = 0;
|
||||
vm->currentOpPc = 0;
|
||||
vm->inErrorHandler = false;
|
||||
vm->errorMsg[0] = '\0';
|
||||
}
|
||||
|
|
@ -391,6 +393,17 @@ BasVmResultE basVmRun(BasVmT *vm) {
|
|||
}
|
||||
}
|
||||
|
||||
// Host callbacks (property setters, method dispatch, etc.) can
|
||||
// abort execution by writing an error message and clearing
|
||||
// vm->running. The step itself returns OK because the VM did
|
||||
// nothing wrong; the failure happened on the host side. If the
|
||||
// host left an error message, surface it as BAS_VM_ERROR so the
|
||||
// caller (IDE, stub) can navigate to the line and stop the
|
||||
// program instead of treating the halt as a normal END.
|
||||
if (vm->errorMsg[0] != '\0') {
|
||||
return BAS_VM_ERROR;
|
||||
}
|
||||
|
||||
return BAS_VM_HALTED;
|
||||
}
|
||||
|
||||
|
|
@ -467,6 +480,7 @@ BasVmResultE basVmStep(BasVmT *vm) {
|
|||
return BAS_VM_HALTED;
|
||||
}
|
||||
|
||||
vm->currentOpPc = vm->pc;
|
||||
uint8_t op = vm->module->code[vm->pc++];
|
||||
|
||||
switch (op) {
|
||||
|
|
|
|||
|
|
@ -389,6 +389,7 @@ typedef struct {
|
|||
int32_t stepLimit; // max steps per basVmRun (0 = unlimited)
|
||||
int32_t stepCount; // steps executed in last basVmRun
|
||||
int32_t currentLine; // source line from last OP_LINE (debugger)
|
||||
int32_t currentOpPc; // PC of the instruction currently executing (set at start of each basVmStep; use this, not vm->pc, for "which SUB are we in" lookups from host callbacks, since vm->pc has already advanced past the opcode + operands)
|
||||
|
||||
// Debug state
|
||||
int32_t *breakpoints; // sorted line numbers (host-managed)
|
||||
|
|
@ -428,6 +429,7 @@ typedef struct {
|
|||
int32_t errorNumber; // current Err number
|
||||
int32_t errorPc; // PC of the instruction that caused the error (for RESUME)
|
||||
int32_t errorNextPc; // PC of the next instruction after error (for RESUME NEXT)
|
||||
int32_t errorLine; // source line where the error was raised (frozen; debuggers use this because currentLine keeps advancing while the error dialog pumps events)
|
||||
bool inErrorHandler; // true when executing error handler code
|
||||
char errorMsg[BAS_VM_ERROR_MSG_LEN]; // current error description
|
||||
|
||||
|
|
|
|||
|
|
@ -703,7 +703,11 @@ static void helpHeadingCalcMinSize(WidgetT *w, const BitmapFontT *font) {
|
|||
w->calcMinH = top + textH + HELP_HEADING1_PAD * 2;
|
||||
} else if (hd->level == 2) {
|
||||
int32_t top = first ? 0 : HELP_HEADING2_TOP;
|
||||
w->calcMinH = top + textH + HELP_HEADING2_PAD + 2;
|
||||
bool nextHasBorder = (w->nextSibling &&
|
||||
(w->nextSibling->type == sHelpCodeTypeId ||
|
||||
w->nextSibling->type == sHelpRuleTypeId));
|
||||
int32_t extra = nextHasBorder ? 0 : 2;
|
||||
w->calcMinH = top + textH + HELP_HEADING2_PAD + extra;
|
||||
} else {
|
||||
int32_t top = first ? 0 : HELP_HEADING3_TOP;
|
||||
w->calcMinH = top + textH;
|
||||
|
|
@ -738,8 +742,19 @@ static void helpHeadingPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const
|
|||
int32_t top = first ? 0 : HELP_HEADING2_TOP;
|
||||
int32_t textY = w->y + top;
|
||||
drawTextN(d, ops, font, w->x + HELP_CONTENT_PAD, textY, hd->text, textLen, colors->contentFg, 0, false);
|
||||
|
||||
// Suppress the underline when the next sibling paints its own
|
||||
// top border, so we don't end up with two nearly-adjacent lines.
|
||||
// Tables and code blocks both use sHelpCodeTypeId; .hr uses
|
||||
// sHelpRuleTypeId.
|
||||
bool nextHasBorder = (w->nextSibling &&
|
||||
(w->nextSibling->type == sHelpCodeTypeId ||
|
||||
w->nextSibling->type == sHelpRuleTypeId));
|
||||
|
||||
if (!nextHasBorder) {
|
||||
int32_t lineY = textY + font->charHeight + 1;
|
||||
drawHLine(d, ops, w->x + HELP_CONTENT_PAD, lineY, w->w - HELP_CONTENT_PAD * 2, colors->windowShadow);
|
||||
}
|
||||
} else {
|
||||
int32_t top = first ? 0 : HELP_HEADING3_TOP;
|
||||
int32_t textY = w->y + top;
|
||||
|
|
|
|||
|
|
@ -132,6 +132,7 @@ static int32_t sAppCount = 0;
|
|||
|
||||
int32_t appMain(DxeAppContextT *ctx);
|
||||
void appShutdown(void);
|
||||
static int32_t appEntryCmpName(const void *a, const void *b);
|
||||
static void buildPmWindow(void);
|
||||
static void desktopUpdate(void);
|
||||
static void onAppButtonClick(WidgetT *w);
|
||||
|
|
@ -166,6 +167,14 @@ void appShutdown(void) {
|
|||
}
|
||||
|
||||
|
||||
// qsort comparator for AppEntryT -- case-insensitive on display name.
|
||||
static int32_t appEntryCmpName(const void *a, const void *b) {
|
||||
const AppEntryT *ea = (const AppEntryT *)a;
|
||||
const AppEntryT *eb = (const AppEntryT *)b;
|
||||
return strcasecmp(ea->name, eb->name);
|
||||
}
|
||||
|
||||
|
||||
// Build the main Program Manager window with app buttons, menus, and status bar.
|
||||
// Window is centered horizontally and placed in the upper quarter vertically
|
||||
// so spawned app windows don't hide behind it.
|
||||
|
|
@ -435,6 +444,11 @@ static void scanAppsDir(void) {
|
|||
sAppFiles = NULL;
|
||||
sAppCount = 0;
|
||||
scanAppsDirRecurse("apps");
|
||||
|
||||
if (sAppCount > 1) {
|
||||
qsort(sAppFiles, sAppCount, sizeof(AppEntryT), (int (*)(const void *, const void *))appEntryCmpName);
|
||||
}
|
||||
|
||||
dvxLog("Progman: found %ld app(s)", (long)sAppCount);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -106,12 +106,8 @@ ResEdit.Show
|
|||
ResList.SetColumns "Name,20|Type,8|Size,12"
|
||||
LblStatus.Caption = "Ready. Use File > Open to load a DXE file."
|
||||
|
||||
|
||||
' ============================================================
|
||||
' Type name helper
|
||||
' ============================================================
|
||||
|
||||
FUNCTION TypeName$(t AS LONG)
|
||||
' Type name helper
|
||||
IF t = RES_TYPE_ICON THEN
|
||||
TypeName$ = "Icon"
|
||||
ELSEIF t = RES_TYPE_TEXT THEN
|
||||
|
|
@ -123,12 +119,8 @@ FUNCTION TypeName$(t AS LONG)
|
|||
END IF
|
||||
END FUNCTION
|
||||
|
||||
|
||||
' ============================================================
|
||||
' Format a byte size for display
|
||||
' ============================================================
|
||||
|
||||
FUNCTION FormatSize$(sz AS LONG)
|
||||
' Format a byte size for display
|
||||
IF sz < 1024 THEN
|
||||
FormatSize$ = STR$(sz) + " B"
|
||||
ELSE
|
||||
|
|
@ -136,12 +128,8 @@ FUNCTION FormatSize$(sz AS LONG)
|
|||
END IF
|
||||
END FUNCTION
|
||||
|
||||
|
||||
' ============================================================
|
||||
' Refresh the resource list from the open handle
|
||||
' ============================================================
|
||||
|
||||
SUB RefreshList
|
||||
' Refresh the resource list from the open handle
|
||||
ResList.Clear
|
||||
|
||||
IF resHandle = 0 THEN
|
||||
|
|
@ -161,12 +149,8 @@ SUB RefreshList
|
|||
LblStatus.Caption = filePath + " - " + STR$(n) + " resource(s)"
|
||||
END SUB
|
||||
|
||||
|
||||
' ============================================================
|
||||
' Close the current file
|
||||
' ============================================================
|
||||
|
||||
SUB CloseFile
|
||||
' Close the current file
|
||||
IF resHandle <> 0 THEN
|
||||
ResClose resHandle
|
||||
resHandle = 0
|
||||
|
|
@ -183,12 +167,8 @@ SUB CloseFile
|
|||
LblStatus.Caption = "No file loaded."
|
||||
END SUB
|
||||
|
||||
|
||||
' ============================================================
|
||||
' Reopen the file (after modification) and refresh
|
||||
' ============================================================
|
||||
|
||||
SUB ReopenAndRefresh
|
||||
' Reopen the file (after modification) and refresh
|
||||
DIM path AS STRING
|
||||
path = filePath
|
||||
|
||||
|
|
@ -210,12 +190,8 @@ SUB ReopenAndRefresh
|
|||
END IF
|
||||
END SUB
|
||||
|
||||
|
||||
' ============================================================
|
||||
' Enable/disable selection-dependent menus
|
||||
' ============================================================
|
||||
|
||||
SUB UpdateMenuState
|
||||
' Enable/disable selection-dependent menus
|
||||
DIM hasSel AS INTEGER
|
||||
hasSel = (ResList.ListIndex >= 0)
|
||||
mnuExtract.Enabled = hasSel
|
||||
|
|
@ -223,11 +199,6 @@ SUB UpdateMenuState
|
|||
mnuEditText.Enabled = hasSel
|
||||
END SUB
|
||||
|
||||
|
||||
' ============================================================
|
||||
' Menu handlers
|
||||
' ============================================================
|
||||
|
||||
SUB mnuOpen_Click
|
||||
DIM path AS STRING
|
||||
path = basFileOpen("Open DXE File", "Applications (*.app)|Widget Modules (*.wgt)|Libraries (*.lib)|All Files (*.*)")
|
||||
|
|
@ -429,11 +400,6 @@ SUB mnuRemove_Click
|
|||
END IF
|
||||
END SUB
|
||||
|
||||
|
||||
' ============================================================
|
||||
' ListView selection change
|
||||
' ============================================================
|
||||
|
||||
SUB ResList_Click
|
||||
UpdateMenuState
|
||||
END SUB
|
||||
|
|
|
|||
|
|
@ -200,8 +200,11 @@ Begin Form WidgetShow
|
|||
End
|
||||
|
||||
Begin StatusBar sbar
|
||||
Caption = "Ready."
|
||||
Weight = 0
|
||||
Begin Label lblStatus
|
||||
Caption = "Ready."
|
||||
Weight = 1
|
||||
End
|
||||
End
|
||||
End
|
||||
|
||||
|
|
@ -233,12 +236,6 @@ OPTION EXPLICIT
|
|||
|
||||
DIM addCount AS INTEGER
|
||||
|
||||
|
||||
SUB setStatus(msg AS STRING)
|
||||
sbar.Caption = msg
|
||||
END SUB
|
||||
|
||||
|
||||
Load WidgetShow
|
||||
WidgetShow.Show
|
||||
|
||||
|
|
@ -279,12 +276,13 @@ tvTree.AddChildItem 4, "Pine"
|
|||
tvTree.SetExpanded 0, -1
|
||||
tvTree.SetExpanded 4, -1
|
||||
|
||||
setStatus "Ready. Hover widgets for descriptions."
|
||||
lblStatus.Caption = "Ready. Hover widgets for descriptions."
|
||||
|
||||
|
||||
' ============================================================
|
||||
' Event handlers
|
||||
' ============================================================
|
||||
SUB setStatus(msg AS STRING)
|
||||
lblStatus.Caption = msg
|
||||
END SUB
|
||||
|
||||
|
||||
SUB tabs_Click
|
||||
setStatus "Tab: " + STR$(tabs.GetActive())
|
||||
|
|
@ -353,14 +351,14 @@ SUB btnDel_Click
|
|||
END SUB
|
||||
|
||||
|
||||
SUB lstItems_Click
|
||||
SUB lstItems_Change
|
||||
IF lstItems.ListIndex >= 0 THEN
|
||||
setStatus "ListBox: " + lstItems.List(lstItems.ListIndex)
|
||||
END IF
|
||||
END SUB
|
||||
|
||||
|
||||
SUB lvFiles_Click
|
||||
SUB lvFiles_Change
|
||||
DIM i AS INTEGER
|
||||
FOR i = 0 TO lvFiles.RowCount() - 1
|
||||
IF lvFiles.IsItemSelected(i) THEN
|
||||
|
|
@ -371,7 +369,7 @@ SUB lvFiles_Click
|
|||
END SUB
|
||||
|
||||
|
||||
SUB tvTree_Click
|
||||
SUB tvTree_Change
|
||||
DIM i AS INTEGER
|
||||
FOR i = 0 TO tvTree.ItemCount() - 1
|
||||
IF tvTree.IsItemSelected(i) THEN
|
||||
|
|
|
|||
|
|
@ -217,9 +217,6 @@ int shellMain(int argc, char *argv[]) {
|
|||
// roughly twice as many scheduling turns as any single app.
|
||||
tsSetPriority(0, TS_PRIORITY_HIGH);
|
||||
|
||||
// Gather system information (CPU, memory, drives, etc.)
|
||||
shellInfoInit(&sCtx);
|
||||
|
||||
// Initialize app slot table
|
||||
shellAppInit();
|
||||
|
||||
|
|
@ -266,6 +263,10 @@ int shellMain(int argc, char *argv[]) {
|
|||
platformVideoEnumModes(logVideoMode, NULL);
|
||||
dvxLog("Selected: %ldx%ld %ldbpp (pitch %ld)", (long)sCtx.display.width, (long)sCtx.display.height, (long)sCtx.display.format.bitsPerPixel, (long)sCtx.display.pitch);
|
||||
|
||||
// Gather system information (CPU, memory, video, drives, etc.)
|
||||
// after dvxInit so the display fields have real values to report.
|
||||
shellInfoInit(&sCtx);
|
||||
|
||||
// Apply mouse preferences
|
||||
const char *wheelStr = prefsGetString(sPrefs, "mouse", "wheel", MOUSE_WHEEL_DIR_DEFAULT);
|
||||
int32_t wheelDir = (strcmp(wheelStr, "reversed") == 0) ? -1 : 1;
|
||||
|
|
|
|||
|
|
@ -2105,6 +2105,11 @@ static void invalidateAllWindows(AppContextT *ctx) {
|
|||
// between top-level menus. Position is clamped to screen edges so the
|
||||
// popup doesn't go off-screen.
|
||||
|
||||
void dvxShowContextMenu(AppContextT *ctx, WindowT *win, MenuT *menu, int32_t screenX, int32_t screenY) {
|
||||
openContextMenu(ctx, win, menu, screenX, screenY);
|
||||
}
|
||||
|
||||
|
||||
static void openContextMenu(AppContextT *ctx, WindowT *win, MenuT *menu, int32_t screenX, int32_t screenY) {
|
||||
if (!menu || menu->itemCount <= 0) {
|
||||
return;
|
||||
|
|
@ -3917,6 +3922,15 @@ void dvxDestroyWindow(AppContextT *ctx, WindowT *win) {
|
|||
dirtyListAdd(&ctx->dirty, 0, iconY, ctx->display.width, iconH);
|
||||
}
|
||||
|
||||
// Clear the modal pointer if it was this window. Callers that open
|
||||
// modal dialogs are supposed to restore the previous modal before
|
||||
// destroying the dialog window, but a crash / longjmp / missed
|
||||
// cleanup can leave modalWindow pointing at freed memory -- every
|
||||
// subsequent click is then dropped by the modal gate.
|
||||
if (ctx->modalWindow == win) {
|
||||
ctx->modalWindow = NULL;
|
||||
}
|
||||
|
||||
wmDestroyWindow(&ctx->stack, win);
|
||||
|
||||
// Dirty icon area again with the updated count (one fewer icon)
|
||||
|
|
@ -4209,6 +4223,13 @@ void dvxInvalidateWindow(AppContextT *ctx, WindowT *win) {
|
|||
// Call the window's paint callback to update the content buffer
|
||||
// before marking the screen dirty. This means raw-paint apps only
|
||||
// need to call dvxInvalidateWindow -- onPaint fires automatically.
|
||||
// Set PAINT_FULL for widget-managed windows: widgetOnPaint only
|
||||
// relays out + background-clears when paintNeeded >= PAINT_FULL,
|
||||
// and callers invalidating "the whole window" (theme changes,
|
||||
// scroll, color-scheme swaps) need that full repaint rather than
|
||||
// the dirty-widget-only partial repaint the default would give.
|
||||
win->paintNeeded = PAINT_FULL;
|
||||
|
||||
if (win->onPaint) {
|
||||
RectT fullRect = {0, 0, win->contentW, win->contentH};
|
||||
WIN_CALLBACK(ctx, win, win->onPaint(win, &fullRect));
|
||||
|
|
|
|||
|
|
@ -238,6 +238,14 @@ void dvxDestroyWindow(AppContextT *ctx, WindowT *win);
|
|||
// Raise a window to the top of the z-order and give it focus.
|
||||
void dvxRaiseWindow(AppContextT *ctx, WindowT *win);
|
||||
|
||||
// Display a context menu at a screen position. `menu` must be a
|
||||
// standalone MenuT allocated with wmCreateMenu() and populated with
|
||||
// wmAddMenuItem() / wmAddMenuSeparator() / wmAddSubMenu(). `win`
|
||||
// supplies the onMenu dispatch target; menu-item clicks fire
|
||||
// win->onMenu(win, id) exactly like menu-bar items. Position is
|
||||
// clamped to screen edges.
|
||||
void dvxShowContextMenu(AppContextT *ctx, WindowT *win, MenuT *menu, int32_t screenX, int32_t screenY);
|
||||
|
||||
// Resize a window to exactly fit its widget tree's computed minimum size
|
||||
// (plus chrome). Used for dialog boxes and other fixed-layout windows
|
||||
// where the window should shrink-wrap its content.
|
||||
|
|
|
|||
|
|
@ -634,6 +634,8 @@ const void *wgtGetApi(const char *name);
|
|||
#define WGT_SIG_RET_STR 18 // const char *fn(const WidgetT *)
|
||||
#define WGT_SIG_STR_INT 19 // void fn(WidgetT *, const char *, int32_t)
|
||||
#define WGT_SIG_INT_STR 20 // void fn(WidgetT *, int32_t, const char *)
|
||||
#define WGT_SIG_STR_STR 21 // void fn(WidgetT *, const char *, const char *)
|
||||
#define WGT_SIG_STR_BOOL 22 // void fn(WidgetT *, const char *, bool)
|
||||
|
||||
// Property descriptor
|
||||
typedef struct {
|
||||
|
|
|
|||
|
|
@ -636,15 +636,13 @@ void widgetOnScroll(WindowT *win, ScrollbarOrientE orient, int32_t value) {
|
|||
(void)orient;
|
||||
(void)value;
|
||||
|
||||
// Repaint with new scroll position. PAINT_FULL is required so
|
||||
// widgetOnPaint re-lays out children at the new root x/y offset;
|
||||
// otherwise children keep their old positions and the content
|
||||
// does not visibly scroll.
|
||||
// Repaint with new scroll position. dvxInvalidateWindow sets
|
||||
// PAINT_FULL so widgetOnPaint re-lays out children at the new
|
||||
// root x/y offset.
|
||||
if (win->widgetRoot) {
|
||||
AppContextT *ctx = wgtGetContext(win->widgetRoot);
|
||||
|
||||
if (ctx) {
|
||||
win->paintNeeded = PAINT_FULL;
|
||||
dvxInvalidateWindow(ctx, win);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -51,6 +51,8 @@ void clearOtherSelections(WidgetT *except);
|
|||
// ============================================================
|
||||
|
||||
bool isWordChar(char c);
|
||||
int32_t textEditVisualCol(const char *buf, int32_t lineStart, int32_t off, int32_t tabW);
|
||||
int32_t textEditVisualColToOff(const char *buf, int32_t len, int32_t lineStart, int32_t targetVC, int32_t tabW);
|
||||
int32_t wordBoundaryLeft(const char *buf, int32_t pos);
|
||||
int32_t wordBoundaryRight(const char *buf, int32_t len, int32_t pos);
|
||||
int32_t wordEnd(const char *buf, int32_t len, int32_t pos);
|
||||
|
|
@ -62,6 +64,240 @@ int32_t wordStart(const char *buf, int32_t pos);
|
|||
|
||||
void textEditSaveUndo(char *buf, int32_t len, int32_t cursor, char *undoBuf, int32_t *pUndoLen, int32_t *pUndoCursor, int32_t bufSize);
|
||||
|
||||
// ============================================================
|
||||
// Multi-line line-offset cache
|
||||
// ============================================================
|
||||
//
|
||||
// Library-owned cache of line-start offsets and tab-expanded visual
|
||||
// lengths for a text buffer. Widget owns the backing buffer; the
|
||||
// cache is a derived index that must be invalidated (dirty) or
|
||||
// incrementally updated whenever the buffer mutates.
|
||||
//
|
||||
// Contract: every mutator call on the cache takes the current buf
|
||||
// and len so any fallback-to-rebuild path has what it needs. If the
|
||||
// cache's tabWidth is changed via textEditLineCacheSetTabWidth it
|
||||
// automatically dirties itself, since visual-length entries depend
|
||||
// on tab stops.
|
||||
typedef struct TextEditLineCacheT {
|
||||
int32_t *lineOffsets; // [lineCount+1], last entry = buffer length sentinel
|
||||
int32_t *lineVisLens; // [lineCount], tab-expanded visual column counts
|
||||
int32_t lineOffsetCap;
|
||||
int32_t lineVisLenCap;
|
||||
int32_t cachedLines; // -1 = dirty, otherwise >= 1
|
||||
int32_t cachedMaxLL; // -1 = unknown
|
||||
int32_t tabWidth; // tab stop width used to compute lineVisLens
|
||||
} TextEditLineCacheT;
|
||||
|
||||
void textEditLineCacheInit(TextEditLineCacheT *lc, int32_t tabWidth);
|
||||
void textEditLineCacheFree(TextEditLineCacheT *lc);
|
||||
void textEditLineCacheDirty(TextEditLineCacheT *lc);
|
||||
void textEditLineCacheSetTabWidth(TextEditLineCacheT *lc, int32_t tabWidth);
|
||||
void textEditLineCacheEnsure(TextEditLineCacheT *lc, const char *buf, int32_t len);
|
||||
void textEditLineCacheNotifyInsert(TextEditLineCacheT *lc, const char *buf, int32_t len, int32_t off, int32_t insertLen);
|
||||
void textEditLineCacheNotifyDelete(TextEditLineCacheT *lc, const char *buf, int32_t len, int32_t off, int32_t deleteLen);
|
||||
int32_t textEditLineCount(TextEditLineCacheT *lc, const char *buf, int32_t len);
|
||||
int32_t textEditLineLen(TextEditLineCacheT *lc, const char *buf, int32_t len, int32_t row);
|
||||
int32_t textEditLineStart(TextEditLineCacheT *lc, const char *buf, int32_t len, int32_t row);
|
||||
int32_t textEditMaxLineLen(TextEditLineCacheT *lc, const char *buf, int32_t len);
|
||||
void textEditOffToRowCol(TextEditLineCacheT *lc, const char *buf, int32_t len, int32_t off, int32_t *row, int32_t *col);
|
||||
int32_t textEditRowColToOff(TextEditLineCacheT *lc, const char *buf, int32_t len, int32_t row, int32_t col);
|
||||
|
||||
// ============================================================
|
||||
// Syntax coloring palette
|
||||
// ============================================================
|
||||
//
|
||||
// Indices returned by a widget-supplied colorize callback. The palette
|
||||
// has hard-coded defaults; any non-zero entry in a caller-supplied
|
||||
// customColors[TEXT_SYNTAX_MAX] overrides the default.
|
||||
|
||||
#define TEXT_SYNTAX_DEFAULT 0
|
||||
#define TEXT_SYNTAX_KEYWORD 1
|
||||
#define TEXT_SYNTAX_STRING 2
|
||||
#define TEXT_SYNTAX_COMMENT 3
|
||||
#define TEXT_SYNTAX_NUMBER 4
|
||||
#define TEXT_SYNTAX_OPERATOR 5
|
||||
#define TEXT_SYNTAX_TYPE 6
|
||||
#define TEXT_SYNTAX_MAX 7
|
||||
|
||||
uint32_t textEditSyntaxColor(const DisplayT *d, uint8_t idx, uint32_t defaultFg, const uint32_t *custom);
|
||||
void textEditDrawColorizedText(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, int32_t x, int32_t y, const char *text, int32_t len, const uint8_t *syntaxColors, int32_t textOff, uint32_t defaultFg, uint32_t bg, const uint32_t *customColors);
|
||||
|
||||
// ============================================================
|
||||
// Scroll adjustment
|
||||
// ============================================================
|
||||
//
|
||||
// Adjust scrollRow/scrollCol so the cursor stays inside the visible
|
||||
// window [visRows x visCols]. Visual column accounts for tab width.
|
||||
void textEditEnsureVisible(TextEditLineCacheT *lc, const char *buf, int32_t len, int32_t cursorRow, int32_t cursorCol, int32_t tabW, int32_t visRows, int32_t visCols, int32_t *pScrollRow, int32_t *pScrollCol);
|
||||
|
||||
// ============================================================
|
||||
// Multi-line paint
|
||||
// ============================================================
|
||||
//
|
||||
// Renders the text content of a multi-line editor: per-line tab
|
||||
// expansion, optional syntax coloring, selection highlight, cursor,
|
||||
// and optional per-line background overrides. All widget chrome
|
||||
// (border, gutter, scrollbars, focus rect) stays with the caller.
|
||||
//
|
||||
// The three scratch buffers (rawSyntax, expandBuf, syntaxBuf) are
|
||||
// caller-owned so the caller controls the truncation policy for
|
||||
// very long lines. Lines longer than scratchLen are drawn up to
|
||||
// that limit and truncated cleanly.
|
||||
typedef struct TextEditPaintHooksT {
|
||||
void (*colorize)(const char *line, int32_t lineLen, uint8_t *colors, void *ctx);
|
||||
void *colorizeCtx;
|
||||
uint32_t (*lineDecorator)(int32_t lineNum, uint32_t *gutterColor, void *ctx);
|
||||
void *lineDecoratorCtx;
|
||||
uint8_t *rawSyntax;
|
||||
char *expandBuf;
|
||||
uint8_t *syntaxBuf;
|
||||
int32_t scratchLen;
|
||||
const uint32_t *customSyntaxColors;
|
||||
} TextEditPaintHooksT;
|
||||
|
||||
void widgetTextEditMultiPaintArea(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors, TextEditLineCacheT *lc, const char *buf, int32_t len, int32_t textX, int32_t textY, int32_t innerW, int32_t visCols, int32_t visRows, int32_t scrollRow, int32_t scrollCol, int32_t cursorRow, int32_t cursorCol, int32_t selAnchor, int32_t selCursor, int32_t tabW, uint32_t fg, uint32_t bg, bool showCursor, const TextEditPaintHooksT *hooks);
|
||||
|
||||
// ============================================================
|
||||
// Multi-line mouse click and drag
|
||||
// ============================================================
|
||||
//
|
||||
// Mouse handling for the text content area. Caller is responsible
|
||||
// for all chrome hit-tests (scrollbar, gutter) before calling.
|
||||
//
|
||||
// widgetTextEditMultiMouseClick places the cursor at the click
|
||||
// position, handling single (cursor + drag anchor), double (select
|
||||
// word), and triple (select line) clicks. Caller gets back updated
|
||||
// cursor/desired/sel state.
|
||||
//
|
||||
// widgetTextEditMultiDragUpdateArea is called during a drag to
|
||||
// auto-scroll the view and extend the selection.
|
||||
void widgetTextEditMultiMouseClick(WidgetT *w, TextEditLineCacheT *lc, const char *buf, int32_t len, int32_t vx, int32_t vy, int32_t textX, int32_t textY, const BitmapFontT *font, int32_t scrollRow, int32_t scrollCol, int32_t tabW, int32_t *pCursorRow, int32_t *pCursorCol, int32_t *pDesiredCol, int32_t *pSelAnchor, int32_t *pSelCursor);
|
||||
void widgetTextEditMultiDragUpdateArea(TextEditLineCacheT *lc, const char *buf, int32_t len, int32_t vx, int32_t vy, int32_t textX, int32_t textY, const BitmapFontT *font, int32_t *pScrollRow, int32_t scrollCol, int32_t visRows, int32_t tabW, int32_t *pCursorRow, int32_t *pCursorCol, int32_t *pDesiredCol, int32_t *pSelCursor);
|
||||
|
||||
// ============================================================
|
||||
// Multi-line key handler
|
||||
// ============================================================
|
||||
//
|
||||
// Options bundle: bundles the small set of mode flags the key
|
||||
// handler needs to know about. Avoids a 20-argument entry point.
|
||||
|
||||
typedef struct TextEditMultiOptionsT {
|
||||
bool autoIndent;
|
||||
bool captureTabs;
|
||||
bool useTabChar;
|
||||
int32_t tabWidth;
|
||||
bool readOnly;
|
||||
} TextEditMultiOptionsT;
|
||||
|
||||
// Runs a single keystroke through the multi-line editing engine.
|
||||
// Handles clipboard (Ctrl+A/C/V/X), undo (Ctrl+Z), navigation
|
||||
// (arrows, Ctrl-arrows, Home/End, Ctrl-Home/Ctrl-End, PgUp/PgDn),
|
||||
// editing (Enter with auto-indent, Backspace, Delete, Tab,
|
||||
// printable characters), and shift-based selection extension.
|
||||
//
|
||||
// The caller owns buf/undoBuf storage; buf must have room for
|
||||
// bufSize - 1 characters plus terminator. The library invalidates
|
||||
// the cache when it knows edits span newlines; incremental
|
||||
// single-byte updates go through the cache's notify paths.
|
||||
//
|
||||
// Fires w->onChange for edits that mutate the buffer. Calls
|
||||
// wgtInvalidatePaint(w) before returning for any keystroke that
|
||||
// visibly changes state.
|
||||
void widgetTextEditMultiOnKey(WidgetT *w, int32_t key, int32_t mod, TextEditLineCacheT *lc, char *buf, int32_t bufSize, int32_t *pLen, int32_t *pCursorRow, int32_t *pCursorCol, int32_t *pDesiredCol, int32_t *pScrollRow, int32_t *pScrollCol, int32_t *pSelAnchor, int32_t *pSelCursor, char *undoBuf, int32_t *pUndoLen, int32_t *pUndoCursor, int32_t visRows, int32_t visCols, const TextEditMultiOptionsT *opts);
|
||||
|
||||
// ============================================================
|
||||
// Text-grid scrollbars
|
||||
// ============================================================
|
||||
//
|
||||
// Scrollbar helpers for widgets that scroll a text grid (lines x
|
||||
// columns) rather than pixels. Unlike the general-purpose bevel
|
||||
// scrollbars, these pass `total`/`visible`/`scroll` in logical
|
||||
// units (lines or columns). Layout is fixed: arrow button at each
|
||||
// end sized `thick` x `thick`, remainder is the thumb track.
|
||||
//
|
||||
// widgetTextScrollbarHitTest returns one of the TEXT_SB_HIT_*
|
||||
// values. TEXT_SB_HIT_THUMB sets *pDragOff to the click offset
|
||||
// from the thumb origin, for use in subsequent drag updates.
|
||||
//
|
||||
// widgetTextScrollbarDragToScroll converts a mouse coordinate
|
||||
// during an active thumb drag to a clamped scroll value.
|
||||
|
||||
#define TEXT_SB_HIT_NONE 0
|
||||
#define TEXT_SB_HIT_UP 1
|
||||
#define TEXT_SB_HIT_DOWN 2
|
||||
#define TEXT_SB_HIT_PAGE_UP 3
|
||||
#define TEXT_SB_HIT_PAGE_DOWN 4
|
||||
#define TEXT_SB_HIT_THUMB 5
|
||||
|
||||
void widgetTextScrollbarDraw(DisplayT *d, const BlitOpsT *ops, const ColorSchemeT *colors, bool vertical, int32_t x, int32_t y, int32_t thick, int32_t len, int32_t total, int32_t visible, int32_t scroll);
|
||||
int32_t widgetTextScrollbarDragToScroll(bool vertical, int32_t mouseCoord, int32_t sbStart, int32_t thick, int32_t len, int32_t total, int32_t visible, int32_t dragOff);
|
||||
int32_t widgetTextScrollbarHitTest(bool vertical, int32_t vx, int32_t vy, int32_t x, int32_t y, int32_t thick, int32_t len, int32_t total, int32_t visible, int32_t scroll, int32_t *pDragOff);
|
||||
|
||||
// ============================================================
|
||||
// Multi-line editor state aggregate
|
||||
// ============================================================
|
||||
//
|
||||
// Groups the per-editor state (buffer, cursor, selection, scroll,
|
||||
// undo slot, line cache) into a single struct. Consumers embed this
|
||||
// in their widget data; the library operates on the individual
|
||||
// fields via pointer parameters (see widgetTextEditMulti* calls).
|
||||
//
|
||||
// Allocation policy: caller owns the buf/undoBuf storage. Use
|
||||
// textEditMultiInit to allocate them (returns false if malloc
|
||||
// fails) and textEditMultiFree to release.
|
||||
typedef struct TextEditMultiT {
|
||||
char *buf;
|
||||
int32_t bufSize;
|
||||
int32_t len;
|
||||
|
||||
int32_t cursorRow;
|
||||
int32_t cursorCol;
|
||||
int32_t desiredCol;
|
||||
|
||||
int32_t scrollRow;
|
||||
int32_t scrollCol;
|
||||
|
||||
int32_t selAnchor;
|
||||
int32_t selCursor;
|
||||
|
||||
char *undoBuf;
|
||||
int32_t undoLen;
|
||||
int32_t undoCursor;
|
||||
|
||||
TextEditLineCacheT lines;
|
||||
} TextEditMultiT;
|
||||
|
||||
bool textEditMultiInit(TextEditMultiT *te, int32_t maxLen, int32_t tabWidth);
|
||||
void textEditMultiFree(TextEditMultiT *te);
|
||||
|
||||
// ============================================================
|
||||
// Multi-line text operations
|
||||
// ============================================================
|
||||
//
|
||||
// Pure text operations: no widget chrome, no scrollbar math (beyond
|
||||
// visRows for centering). The caller invalidates paint / ensures
|
||||
// visibility using its own conventions after these return.
|
||||
//
|
||||
// textEditFindNext: searches forward/backward from the current
|
||||
// cursor position. If found, selects the match and moves the cursor
|
||||
// to the match's start (forward) or end (backward). Returns true
|
||||
// on match, false on no-match (no wrap-around).
|
||||
//
|
||||
// textEditReplaceAll: replaces every occurrence of needle with
|
||||
// replacement. Records a single undo snapshot before any change.
|
||||
// Returns the number of replacements made.
|
||||
//
|
||||
// textEditGoToLine: places cursor at the start of the given
|
||||
// 1-based line, selects the entire line, and scrolls so the line
|
||||
// sits ~1/4 from the top of the visible area (with clamping).
|
||||
//
|
||||
// textEditGetWordAtCursor: writes the word under the cursor into
|
||||
// the caller's buffer. Returns the length written (0 if no word).
|
||||
bool textEditFindNext(TextEditLineCacheT *lc, const char *buf, int32_t len, const char *needle, bool caseSensitive, bool forward, int32_t *pCursorRow, int32_t *pCursorCol, int32_t *pDesiredCol, int32_t *pSelAnchor, int32_t *pSelCursor);
|
||||
int32_t textEditGetWordAtCursor(TextEditLineCacheT *lc, const char *buf, int32_t len, int32_t cursorRow, int32_t cursorCol, char *out, int32_t outSize);
|
||||
void textEditGoToLine(TextEditLineCacheT *lc, const char *buf, int32_t len, int32_t line, int32_t visRows, int32_t *pCursorRow, int32_t *pCursorCol, int32_t *pDesiredCol, int32_t *pScrollRow, int32_t *pScrollCol, int32_t *pSelAnchor, int32_t *pSelCursor);
|
||||
int32_t textEditReplaceAll(TextEditLineCacheT *lc, char *buf, int32_t bufSize, int32_t *pLen, const char *needle, const char *replacement, bool caseSensitive, int32_t *pCursorRow, int32_t *pCursorCol, int32_t *pDesiredCol, int32_t *pSelAnchor, int32_t *pSelCursor, char *undoBuf, int32_t *pUndoLen, int32_t *pUndoCursor);
|
||||
|
||||
// ============================================================
|
||||
// Single-line text editing engine
|
||||
// ============================================================
|
||||
|
|
|
|||
|
|
@ -689,6 +689,12 @@ int32_t hlpcCompile(const char **inputFiles, int32_t inputCount, const char *out
|
|||
pass2Wrap();
|
||||
progressStep();
|
||||
|
||||
// Sort index entries alphabetically. Done once here so both the
|
||||
// HTML sidebar and the binary index section see the same order.
|
||||
if (indexCount > 1) {
|
||||
qsort(indexEntries, indexCount, sizeof(IndexEntryT), compareIndexEntries);
|
||||
}
|
||||
|
||||
// HTML output (uses wrapped text, before binary passes)
|
||||
if (htmlPath) {
|
||||
if (emitHtml(htmlPath) == 0) {
|
||||
|
|
@ -1523,8 +1529,7 @@ static int pass5Serialize(const char *outputPath) {
|
|||
offset += sizeof(entry);
|
||||
}
|
||||
|
||||
// --- 4. Keyword index entries (sorted) ---
|
||||
qsort(indexEntries, indexCount, sizeof(IndexEntryT), compareIndexEntries);
|
||||
// --- 4. Keyword index entries (sorted in compile() after pass2) ---
|
||||
hdr.indexOffset = offset;
|
||||
hdr.indexCount = indexCount;
|
||||
for (int32_t i = 0; i < indexCount; i++) {
|
||||
|
|
|
|||
|
|
@ -96,7 +96,7 @@ define WIDGET_RULES
|
|||
$(OBJDIR)/$(word 3,$(subst :, ,$1)).o: $(word 2,$(subst :, ,$1))/$(word 3,$(subst :, ,$1)).c $$(WIDGET_DEPS_H) | $(OBJDIR)
|
||||
$$(CC) $$(CFLAGS) -c -o $$@ $$<
|
||||
|
||||
$(WGTDIR)/$(word 1,$(subst :, ,$1))/$(word 1,$(subst :, ,$1)).wgt: $(OBJDIR)/$(word 3,$(subst :, ,$1)).o | $(WGTDIR)/$(word 1,$(subst :, ,$1))
|
||||
$(WGTDIR)/$(word 1,$(subst :, ,$1))/$(word 1,$(subst :, ,$1)).wgt: $(OBJDIR)/$(word 3,$(subst :, ,$1)).o $$(wildcard $(word 2,$(subst :, ,$1))/$(word 4,$(subst :, ,$1)).res) $$(wildcard $(word 2,$(subst :, ,$1))/*.bmp) | $(WGTDIR)/$(word 1,$(subst :, ,$1))
|
||||
$$(DXE3GEN) -o $(WGTDIR)/$(word 1,$(subst :, ,$1))/$(word 1,$(subst :, ,$1)).dxe -U $$<
|
||||
mv $(WGTDIR)/$(word 1,$(subst :, ,$1))/$(word 1,$(subst :, ,$1)).dxe $$@
|
||||
@if [ -f $(word 2,$(subst :, ,$1))/$(word 4,$(subst :, ,$1)).res ]; then \
|
||||
|
|
|
|||
|
|
@ -1045,7 +1045,11 @@ static const WgtMethodDescT sMethods[] = {
|
|||
};
|
||||
|
||||
static const WgtIfaceT sIface = {
|
||||
.basName = "Canvas",
|
||||
// VB-compatible name is "PictureBox" -- matches what the rest of
|
||||
// the docs (ideguide, canvas.bhs) and sample .frm files (iconed)
|
||||
// already use. Prior "Canvas" basName caused silent frm-load
|
||||
// drops whenever an app asked for a PictureBox.
|
||||
.basName = "PictureBox",
|
||||
.props = NULL,
|
||||
.propCount = 0,
|
||||
.methods = sMethods,
|
||||
|
|
|
|||
|
|
@ -99,8 +99,13 @@ void widgetCheckboxAccelActivate(WidgetT *w, WidgetT *root) {
|
|||
// relative to each other regardless of font size.
|
||||
void widgetCheckboxCalcMinSize(WidgetT *w, const BitmapFontT *font) {
|
||||
CheckboxDataT *d = (CheckboxDataT *)w->data;
|
||||
// +1 on the width: widgetCheckboxPaint draws the focus rect from
|
||||
// labelX-1 with width labelW+2, whose rightmost column lands one
|
||||
// pixel past the widget's claimed right edge. Without the +1,
|
||||
// rectFill in the next paint doesn't clear that column and the
|
||||
// stale dot survives after focus moves away.
|
||||
w->calcMinW = CHECKBOX_BOX_SIZE + CHECKBOX_GAP +
|
||||
textWidthAccel(font, d->text);
|
||||
textWidthAccel(font, d->text) + 1;
|
||||
w->calcMinH = DVX_MAX(CHECKBOX_BOX_SIZE, font->charHeight);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ typedef struct {
|
|||
void (*setColumnHeader)(WidgetT *w, const char *fieldName, const char *header);
|
||||
void (*setColumnWidth)(WidgetT *w, const char *fieldName, int32_t width);
|
||||
int32_t (*getSelectedRow)(const WidgetT *w);
|
||||
void (*setSelectedRow)(WidgetT *w, int32_t row);
|
||||
} DbGridApiT;
|
||||
|
||||
static inline const DbGridApiT *dvxDbGridApi(void) {
|
||||
|
|
@ -48,5 +49,6 @@ static inline const DbGridApiT *dvxDbGridApi(void) {
|
|||
#define wgtDbGridSetColumnHeader(w, field, hdr) dvxDbGridApi()->setColumnHeader(w, field, hdr)
|
||||
#define wgtDbGridSetColumnWidth(w, field, width) dvxDbGridApi()->setColumnWidth(w, field, width)
|
||||
#define wgtDbGridGetSelectedRow(w) dvxDbGridApi()->getSelectedRow(w)
|
||||
#define wgtDbGridSetSelectedRow(w, row) dvxDbGridApi()->setSelectedRow(w, row)
|
||||
|
||||
#endif // DBGRID_H
|
||||
|
|
|
|||
|
|
@ -145,6 +145,7 @@ static void dbGridDestroy(WidgetT *w);
|
|||
static int32_t dbGridGetCursorShape(WidgetT *w, int32_t mx, int32_t my);
|
||||
static bool dbGridGetGridLines(const WidgetT *w);
|
||||
int32_t dbGridGetSelectedRow(const WidgetT *w);
|
||||
void dbGridSetSelectedRow(WidgetT *w, int32_t row);
|
||||
static void dbGridOnDragEnd(WidgetT *w);
|
||||
static void dbGridOnDragUpdate(WidgetT *w, WidgetT *root, int32_t x, int32_t y);
|
||||
static void dbGridOnKey(WidgetT *w, int32_t key, int32_t mod);
|
||||
|
|
@ -384,6 +385,40 @@ int32_t dbGridGetSelectedRow(const WidgetT *w) {
|
|||
}
|
||||
|
||||
|
||||
// Programmatically select a data-row (same effect as a user click).
|
||||
// Clamps to the current row range, syncs the bound Data control's
|
||||
// cursor (which cascades to any controls bound to that Data via
|
||||
// the normal Reposition path), and repaints. -1 deselects.
|
||||
void dbGridSetSelectedRow(WidgetT *w, int32_t row) {
|
||||
if (!w || !w->data) {
|
||||
return;
|
||||
}
|
||||
|
||||
DbGridDataT *d = (DbGridDataT *)w->data;
|
||||
int32_t rowCount = getDataRowCount(d);
|
||||
|
||||
if (row < -1) {
|
||||
row = -1;
|
||||
}
|
||||
|
||||
if (row >= rowCount) {
|
||||
row = rowCount - 1;
|
||||
}
|
||||
|
||||
if (d->selectedRow == row) {
|
||||
return;
|
||||
}
|
||||
|
||||
d->selectedRow = row;
|
||||
|
||||
if (d->dataWidget && row >= 0) {
|
||||
wgtDataCtrlSetCurrentRow(d->dataWidget, row);
|
||||
}
|
||||
|
||||
wgtInvalidatePaint(w);
|
||||
}
|
||||
|
||||
|
||||
static void dbGridOnDragEnd(WidgetT *w) {
|
||||
DbGridDataT *d = (DbGridDataT *)w->data;
|
||||
d->sbDragOrient = -2;
|
||||
|
|
@ -1191,6 +1226,7 @@ static const struct {
|
|||
void (*setColumnHeader)(WidgetT *w, const char *fieldName, const char *header);
|
||||
void (*setColumnWidth)(WidgetT *w, const char *fieldName, int32_t width);
|
||||
int32_t (*getSelectedRow)(const WidgetT *w);
|
||||
void (*setSelectedRow)(WidgetT *w, int32_t row);
|
||||
} sApi = {
|
||||
.create = dbGridCreate,
|
||||
.setDataWidget = dbGridSetDataWidget,
|
||||
|
|
@ -1199,14 +1235,19 @@ static const struct {
|
|||
.setColumnHeader = dbGridSetColumnHeader,
|
||||
.setColumnWidth = dbGridSetColumnWidth,
|
||||
.getSelectedRow = dbGridGetSelectedRow,
|
||||
.setSelectedRow = dbGridSetSelectedRow,
|
||||
};
|
||||
|
||||
static const WgtPropDescT sProps[] = {
|
||||
{ "GridLines", WGT_IFACE_BOOL, (void *)dbGridGetGridLines, (void *)dbGridSetGridLines, NULL },
|
||||
{ "SelectedRow", WGT_IFACE_INT, (void *)dbGridGetSelectedRow, (void *)dbGridSetSelectedRow, NULL },
|
||||
};
|
||||
|
||||
static const WgtMethodDescT sMethods[] = {
|
||||
{ "Refresh", WGT_SIG_VOID, (void *)dbGridRefresh },
|
||||
{ "SetColumnVisible", WGT_SIG_STR_BOOL, (void *)dbGridSetColumnVisible },
|
||||
{ "SetColumnHeader", WGT_SIG_STR_STR, (void *)dbGridSetColumnHeader },
|
||||
{ "SetColumnWidth", WGT_SIG_STR_INT, (void *)dbGridSetColumnWidth },
|
||||
};
|
||||
|
||||
static const WgtEventDescT sEvents[] = {
|
||||
|
|
@ -1217,11 +1258,11 @@ static const WgtEventDescT sEvents[] = {
|
|||
static const WgtIfaceT sIface = {
|
||||
.basName = "DBGrid",
|
||||
.props = sProps,
|
||||
.propCount = 1,
|
||||
.propCount = sizeof(sProps) / sizeof(sProps[0]),
|
||||
.methods = sMethods,
|
||||
.methodCount = 1,
|
||||
.methodCount = sizeof(sMethods) / sizeof(sMethods[0]),
|
||||
.events = sEvents,
|
||||
.eventCount = 2,
|
||||
.eventCount = sizeof(sEvents) / sizeof(sEvents[0]),
|
||||
.createSig = WGT_CREATE_PARENT,
|
||||
.isContainer = false,
|
||||
.defaultEvent = "DblClick",
|
||||
|
|
|
|||
|
|
@ -26,9 +26,14 @@
|
|||
|
||||
#include "../../../libs/kpunch/libdvx/dvxWgt.h"
|
||||
|
||||
// Field order mirrors widgetRadio.c's sApi. `create` must be first
|
||||
// because the BASIC form runtime invokes widget constructors by
|
||||
// dereferencing the api pointer as a pointer-to-function-pointer
|
||||
// (see createWidgetByIface). If the order is changed here, update
|
||||
// widgetRadio.c to match.
|
||||
typedef struct {
|
||||
WidgetT *(*group)(WidgetT *parent);
|
||||
WidgetT *(*create)(WidgetT *parent, const char *text);
|
||||
WidgetT *(*group)(WidgetT *parent);
|
||||
void (*groupSetSelected)(WidgetT *w, int32_t idx);
|
||||
int32_t (*getIndex)(const WidgetT *w);
|
||||
} RadioApiT;
|
||||
|
|
|
|||
|
|
@ -55,6 +55,12 @@ static int32_t sRadioTypeId = -1;
|
|||
typedef struct {
|
||||
const char *text;
|
||||
int32_t index;
|
||||
// `selected` is the source of truth for .frm-loaded OptionButtons,
|
||||
// whose parent is a plain layout container rather than a
|
||||
// RadioGroup. When the parent IS a RadioGroup, the group's
|
||||
// selectedIdx still wins (the group view is canonical and the C
|
||||
// API callers like dvxdemo keep using it).
|
||||
bool selected;
|
||||
} RadioDataT;
|
||||
|
||||
typedef struct {
|
||||
|
|
@ -229,8 +235,13 @@ void widgetRadioAccelActivate(WidgetT *w, WidgetT *root) {
|
|||
// column, and the indicator + label layout is visually consistent.
|
||||
void widgetRadioCalcMinSize(WidgetT *w, const BitmapFontT *font) {
|
||||
RadioDataT *d = (RadioDataT *)w->data;
|
||||
// +1 on the width: widgetRadioPaint draws the focus rect from
|
||||
// labelX-1 with width labelW+2, whose rightmost column lands at
|
||||
// w->x + (BOX+GAP+labelW). Without the +1 that column is one
|
||||
// pixel past the widget's claimed right edge, so the rectFill
|
||||
// in the next paint doesn't clear it and the focus dot survives.
|
||||
w->calcMinW = CHECKBOX_BOX_SIZE + CHECKBOX_GAP +
|
||||
textWidthAccel(font, d->text);
|
||||
textWidthAccel(font, d->text) + 1;
|
||||
w->calcMinH = DVX_MAX(CHECKBOX_BOX_SIZE, font->charHeight);
|
||||
}
|
||||
|
||||
|
|
@ -244,25 +255,56 @@ void widgetRadioCalcMinSize(WidgetT *w, const BitmapFontT *font) {
|
|||
//
|
||||
// Key codes use DOS BIOS scancode convention: high byte 0x01 flag
|
||||
// ORed with the scancode. 0x50=Down, 0x4D=Right, 0x48=Up, 0x4B=Left.
|
||||
// Selects `target` within `parent` -- parent may be a RadioGroup (C
|
||||
// API flow, updates group selectedIdx) or a plain layout container
|
||||
// (.frm-loaded OptionButton flow, updates each RadioDataT.selected).
|
||||
static void selectRadio(WidgetT *parent, WidgetT *target) {
|
||||
if (!parent || !target) {
|
||||
return;
|
||||
}
|
||||
|
||||
RadioDataT *td = (RadioDataT *)target->data;
|
||||
|
||||
if (parent->type == sRadioGroupTypeId) {
|
||||
RadioGroupDataT *gd = (RadioGroupDataT *)parent->data;
|
||||
invalidateOldSelection(parent, gd->selectedIdx);
|
||||
gd->selectedIdx = td ? td->index : 0;
|
||||
} else {
|
||||
for (WidgetT *c = parent->firstChild; c; c = c->nextSibling) {
|
||||
if (c->type == sRadioTypeId && c != target) {
|
||||
RadioDataT *sd = (RadioDataT *)c->data;
|
||||
|
||||
if (sd && sd->selected) {
|
||||
sd->selected = false;
|
||||
wgtInvalidatePaint(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (td) {
|
||||
td->selected = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (parent->type == sRadioGroupTypeId && parent->onChange) {
|
||||
parent->onChange(parent);
|
||||
} else if (parent->type != sRadioGroupTypeId && target->onChange) {
|
||||
target->onChange(target);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void widgetRadioOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
||||
(void)mod;
|
||||
RadioDataT *rd = (RadioDataT *)w->data;
|
||||
|
||||
if (!w->parent) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (key == ' ' || key == 0x0D) {
|
||||
// Select this radio
|
||||
if (w->parent && w->parent->type == sRadioGroupTypeId) {
|
||||
RadioGroupDataT *gd = (RadioGroupDataT *)w->parent->data;
|
||||
gd->selectedIdx = rd->index;
|
||||
|
||||
if (w->parent->onChange) {
|
||||
w->parent->onChange(w->parent);
|
||||
}
|
||||
}
|
||||
|
||||
selectRadio(w->parent, w);
|
||||
wgtInvalidatePaint(w);
|
||||
} else if (key == KEY_DOWN || key == KEY_RIGHT) {
|
||||
// Down or Right -- next radio in group
|
||||
if (w->parent && w->parent->type == sRadioGroupTypeId) {
|
||||
WidgetT *next = NULL;
|
||||
|
||||
for (WidgetT *s = w->nextSibling; s; s = s->nextSibling) {
|
||||
|
|
@ -273,22 +315,12 @@ void widgetRadioOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
|||
}
|
||||
|
||||
if (next) {
|
||||
RadioDataT *nd = (RadioDataT *)next->data;
|
||||
RadioGroupDataT *gd = (RadioGroupDataT *)next->parent->data;
|
||||
invalidateOldSelection(w->parent, gd->selectedIdx);
|
||||
wgtInvalidatePaint(w); // erase focus rect on widget we are leaving
|
||||
sFocusedWidget = next;
|
||||
gd->selectedIdx = nd->index;
|
||||
|
||||
if (next->parent->onChange) {
|
||||
next->parent->onChange(next->parent);
|
||||
}
|
||||
|
||||
selectRadio(w->parent, next);
|
||||
wgtInvalidatePaint(next);
|
||||
}
|
||||
}
|
||||
} else if (key == KEY_UP || key == KEY_LEFT) {
|
||||
// Up or Left -- previous radio in group
|
||||
if (w->parent && w->parent->type == sRadioGroupTypeId) {
|
||||
WidgetT *prev = NULL;
|
||||
|
||||
for (WidgetT *s = w->parent->firstChild; s && s != w; s = s->nextSibling) {
|
||||
|
|
@ -298,20 +330,12 @@ void widgetRadioOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
|||
}
|
||||
|
||||
if (prev) {
|
||||
RadioDataT *pd = (RadioDataT *)prev->data;
|
||||
RadioGroupDataT *gd = (RadioGroupDataT *)prev->parent->data;
|
||||
invalidateOldSelection(w->parent, gd->selectedIdx);
|
||||
wgtInvalidatePaint(w); // erase focus rect on widget we are leaving
|
||||
sFocusedWidget = prev;
|
||||
gd->selectedIdx = pd->index;
|
||||
|
||||
if (prev->parent->onChange) {
|
||||
prev->parent->onChange(prev->parent);
|
||||
}
|
||||
|
||||
selectRadio(w->parent, prev);
|
||||
wgtInvalidatePaint(prev);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -321,8 +345,9 @@ void widgetRadioOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy) {
|
|||
(void)vy;
|
||||
sFocusedWidget = w;
|
||||
|
||||
if (w->parent && w->parent->type == sRadioGroupTypeId) {
|
||||
RadioDataT *rd = (RadioDataT *)w->data;
|
||||
|
||||
if (w->parent && w->parent->type == sRadioGroupTypeId) {
|
||||
RadioGroupDataT *gd = (RadioGroupDataT *)w->parent->data;
|
||||
|
||||
if (gd->selectedIdx != rd->index) {
|
||||
|
|
@ -334,7 +359,34 @@ void widgetRadioOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy) {
|
|||
if (w->parent->onChange) {
|
||||
w->parent->onChange(w->parent);
|
||||
}
|
||||
} else if (w->parent) {
|
||||
// .frm-loaded OptionButton: no enclosing RadioGroup. Walk
|
||||
// sibling OptionButtons, clear theirs, mark this one selected.
|
||||
for (WidgetT *c = w->parent->firstChild; c; c = c->nextSibling) {
|
||||
if (c->type == sRadioTypeId && c != w) {
|
||||
RadioDataT *sd = (RadioDataT *)c->data;
|
||||
|
||||
if (sd && sd->selected) {
|
||||
sd->selected = false;
|
||||
wgtInvalidatePaint(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (rd) {
|
||||
rd->selected = true;
|
||||
}
|
||||
|
||||
if (w->onChange) {
|
||||
w->onChange(w);
|
||||
}
|
||||
}
|
||||
|
||||
// Always repaint the clicked radio: its selection dot or focus rect
|
||||
// may need to appear. The mouse dispatcher invalidates the previous
|
||||
// focus owner, but nothing invalidates the newly-focused widget
|
||||
// unless we do it here.
|
||||
wgtInvalidatePaint(w);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -389,11 +441,22 @@ void widgetRadioPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bitmap
|
|||
drawHLine(d, ops, bx + right, boxY + i, 1, sh);
|
||||
}
|
||||
|
||||
// Draw filled diamond if selected
|
||||
// Draw filled diamond if selected. Two sources of truth: the
|
||||
// RadioGroup's selectedIdx (when parent is a RadioGroup, via C
|
||||
// API or wgtRadioGroupSetSelected), or this widget's own
|
||||
// rd->selected bit (set by mouse/key handlers when the radio is
|
||||
// just a child of a layout container, as happens for
|
||||
// .frm-loaded OptionButtons).
|
||||
bool isSelected = false;
|
||||
|
||||
if (w->parent && w->parent->type == sRadioGroupTypeId) {
|
||||
RadioGroupDataT *gd = (RadioGroupDataT *)w->parent->data;
|
||||
isSelected = (gd->selectedIdx == rd->index);
|
||||
} else {
|
||||
isSelected = rd->selected;
|
||||
}
|
||||
|
||||
if (gd->selectedIdx == rd->index) {
|
||||
if (isSelected) {
|
||||
uint32_t dotFg = w->enabled ? fg : colors->windowShadow;
|
||||
|
||||
static const int32_t dotW[] = {2, 4, 6, 6, 4, 2};
|
||||
|
|
@ -403,7 +466,6 @@ void widgetRadioPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bitmap
|
|||
drawHLine(d, ops, bx + mid - dw / 2, boxY + mid - 3 + i, dw, dotFg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int32_t labelX = w->x + CHECKBOX_BOX_SIZE + CHECKBOX_GAP;
|
||||
int32_t labelY = w->y + (w->h - font->charHeight) / 2;
|
||||
|
|
|
|||
|
|
@ -67,6 +67,15 @@ WidgetT *wgtHSeparator(WidgetT *parent) {
|
|||
|
||||
if (w) {
|
||||
SeparatorDataT *d = calloc(1, sizeof(SeparatorDataT));
|
||||
// Auto-orient to the parent: horizontal container -> vertical
|
||||
// divider, vertical container -> horizontal divider. This
|
||||
// lets a single "Line" basName work correctly in both
|
||||
// toolbars and menus without requiring callers to pick
|
||||
// between HSeparator and VSeparator by widget type.
|
||||
if (parent && widgetIsHorizContainer(parent->type)) {
|
||||
d->vertical = true;
|
||||
}
|
||||
|
||||
w->data = d;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -41,7 +41,11 @@
|
|||
|
||||
#include "dvxWgtP.h"
|
||||
|
||||
#define TOOLBAR_PAD 2
|
||||
// Tight padding keeps the status bar visually a single-line strip
|
||||
// (VB-style). The per-child sunken bevel already adds 1px of visual
|
||||
// inset around each panel, so additional container padding just makes
|
||||
// the bar look chunky.
|
||||
#define TOOLBAR_PAD 1
|
||||
#define TOOLBAR_GAP 2
|
||||
|
||||
static int32_t sTypeId = -1;
|
||||
|
|
@ -87,7 +91,8 @@ static const WgtIfaceT sIface = {
|
|||
.methodCount = 0,
|
||||
.events = NULL,
|
||||
.eventCount = 0,
|
||||
.createSig = WGT_CREATE_PARENT
|
||||
.createSig = WGT_CREATE_PARENT,
|
||||
.isContainer = true
|
||||
};
|
||||
|
||||
void wgtRegister(void) {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
icon24 icon tabctrl.bmp
|
||||
name text "TabControl"
|
||||
name text "TabStrip"
|
||||
icon24-2 icon tabctrl.bmp
|
||||
name-2 text "TabPage"
|
||||
author text "Scott Duensing"
|
||||
copyright text "Copyright 2026 Scott Duensing"
|
||||
publisher text "Kangaroo Punch Studios"
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -854,7 +854,24 @@ bool wgtTreeItemIsSelected(const WidgetT *w) {
|
|||
VALIDATE_WIDGET(w, sTreeItemTypeId, false);
|
||||
|
||||
TreeItemDataT *ti = (TreeItemDataT *)w->data;
|
||||
return ti->selected;
|
||||
|
||||
// ti->selected tracks multi-select state (Ctrl-click, shift-click
|
||||
// range). In single-select mode, the current selection lives on
|
||||
// the TreeView's tv->selectedItem pointer instead -- walk up to
|
||||
// the owning TreeView and compare, so callers see the expected
|
||||
// answer for both modes.
|
||||
if (ti->selected) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (WidgetT *p = w->parent; p; p = p->parent) {
|
||||
if (p->type == sTreeViewTypeId) {
|
||||
TreeViewDataT *tv = (TreeViewDataT *)p->data;
|
||||
return tv && tv->selectedItem == w;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -923,17 +940,26 @@ void wgtTreeViewSetSelected(WidgetT *w, WidgetT *item) {
|
|||
|
||||
TreeViewDataT *tv = (TreeViewDataT *)w->data;
|
||||
|
||||
// Expand all ancestors so the item is visible in the tree
|
||||
// Expand all ancestors so the item is visible in the tree.
|
||||
// Invalidate cached dims so the scroll-into-view below sees the
|
||||
// correct post-expansion totalH / item Y coordinate.
|
||||
if (item) {
|
||||
bool expandedAny = false;
|
||||
|
||||
for (WidgetT *p = item->parent; p && p != w; p = p->parent) {
|
||||
if (p->type == sTreeItemTypeId) {
|
||||
TreeItemDataT *pti = (TreeItemDataT *)p->data;
|
||||
|
||||
if (pti && !pti->expanded) {
|
||||
pti->expanded = true;
|
||||
expandedAny = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (expandedAny) {
|
||||
tv->dimsValid = false;
|
||||
}
|
||||
}
|
||||
|
||||
setSelectedItem(w, item);
|
||||
|
|
@ -945,7 +971,37 @@ void wgtTreeViewSetSelected(WidgetT *w, WidgetT *item) {
|
|||
tv->anchorItem = item;
|
||||
}
|
||||
|
||||
wgtInvalidatePaint(w);
|
||||
// Scroll so the selected item is inside the visible band. Manual
|
||||
// expand+click goes through widgetTreeViewOnMouse / OnKey which
|
||||
// have their own scroll-into-view logic; programmatic callers
|
||||
// (e.g. dvxhelp syncing the TOC from navigateToTopic) need this.
|
||||
if (item) {
|
||||
AppContextT *ctx = wgtGetContext(w);
|
||||
|
||||
if (ctx) {
|
||||
const BitmapFontT *font = &ctx->font;
|
||||
int32_t itemY = treeItemYPos(w, item, font);
|
||||
|
||||
if (itemY >= 0) {
|
||||
int32_t totalH;
|
||||
int32_t totalW;
|
||||
int32_t innerH;
|
||||
int32_t innerW;
|
||||
bool needVSb;
|
||||
bool needHSb;
|
||||
|
||||
treeCalcScrollbarNeeds(w, font, &totalH, &totalW, &innerH, &innerW, &needVSb, &needHSb);
|
||||
|
||||
if (itemY < tv->scrollPos) {
|
||||
tv->scrollPos = itemY;
|
||||
} else if (itemY + font->charHeight > tv->scrollPos + innerH) {
|
||||
tv->scrollPos = itemY + font->charHeight - innerH;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
wgtInvalidate(w);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -997,10 +1053,15 @@ static void widgetTreeViewDestroy(WidgetT *w) {
|
|||
void widgetTreeViewLayout(WidgetT *w, const BitmapFontT *font) {
|
||||
TreeViewDataT *tv = (TreeViewDataT *)w->data;
|
||||
|
||||
// Auto-select first item if nothing is selected
|
||||
// Auto-select first item if nothing is selected. This runs at
|
||||
// layout time (not user action), so set tv->selectedItem directly
|
||||
// instead of going through setSelectedItem -- the latter fires
|
||||
// onChange as if the user clicked, which triggers callers (e.g.
|
||||
// dvxhelp's TOC sync) to re-navigate to the first tree item and
|
||||
// clobber any programmatic selection the host is about to make.
|
||||
if (!tv->selectedItem) {
|
||||
WidgetT *first = firstVisibleItem(w);
|
||||
setSelectedItem(w, first);
|
||||
tv->selectedItem = first;
|
||||
|
||||
if (tv->multiSelect && first) {
|
||||
((TreeItemDataT *)first->data)->selected = true;
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue