Hi!
I’m new to the group and just happen to stumble into this conversation. If you don’t mind I further refined the utility code. This updated code significantly enhances the file browser by replacing “magic numbers” with descriptive constants for improved readability and maintainability. Key navigation has been upgraded with explicit sorting, dedicated Home and End key support, and refined Page Up/Down logic for more predictable scrolling. The TextInput$
function is now much more user-friendly, featuring **arrow key navigation, character insertion, and **case-sensitive input. Additionally, the ‘dirwin$’ function introduces an interactive “F” key filter, displays the file/directory count, ensures clearer redrawing of the file list, and implements more robust error handling with centralized error messages for path and drive operations. Finally, the user interface now includes better labels in the command bar, making all new features easily discoverable.
'----------------------------
'Utility library - Enhanced
'----------------------------
CLEAR
OPTION base 1
OPTION EXPLICIT
’ — Global Constants —
CONST COLOR_BACKGROUND_LIST = 145 ’ A typical light blue/gray
CONST COLOR_BOX_BORDER = 666 ’ A darker border color
CONST DIR_LIST_WIDTH = 39 ’ Width of the directory listing area in characters
CONST KEY_UP = 128
CONST KEY_DOWN = 129
CONST KEY_PAGE_UP = 136
CONST KEY_PAGE_DOWN = 137
CONST KEY_HOME = 134
CONST KEY_END = 135
CONST KEY_ENTER = 13
CONST KEY_ESCAPE = 27
CONST KEY_BACKSPACE = 8 ’ Assuming this is 8 for backspace
CONST DIR_ENTRY_PREFIX = “1” ’ Prefix for directory entries in fname$()
CONST FILE_ENTRY_PREFIX = “2” ’ Prefix for file entries in fname$()
CONST SORT_MODE_DIRS_FIRST = 2 ’ Sort directories before files
CONST SORT_MODE_ALL_ALPHA = 3 ’ Sort all alphabetically
’ — Global Variables —
DIM fontw AS INTEGER= MM.FONTWIDTH
DIM fonth AS INTEGER= MM.FONTHEIGHT
’ — Main Program Entry Point —
CALL MainProgram
SUB MainProgram
LOCAL selectedItemPath$
CLS
PRINT “Welcome to the Enhanced File Browser!”
PRINT “”
PRINT “Press any key to open the file browser…”
DO : LOOP WHILE INKEY$ = “” ’ Wait for a key press
' Example usage of the enhanced dirwin$
' It returns the full path and selected item name, separated by '$'
selectedItemPath$ = DirWindow$(0, 0, 24, "B:/", "") ' Start in B:\, showing 24 lines
IF LEFT$(selectedItemPath$, 3) = "999" THEN ' Check for cancellation string
PRINT @(0, fonth * 26) "Operation cancelled."
ELSE
LOCAL currentPath$, selectedItemName$
currentPath$ = LEFT$(selectedItemPath$, INSTR(1, selectedItemPath$, "$") - 1)
selectedItemName$ = RIGHT$(selectedItemPath$, LEN(selectedItemPath$) - INSTR(1, selectedItemPath$, "$"))
PRINT @(0, fonth * 26) "Selected Path: " + currentPath$
PRINT @(0, fonth * 27) "Selected Item: " + selectedItemName$
END IF
PRINT ""
PRINT "Press any key to exit."
DO : LOOP WHILE INKEY$ = ""
END
END SUB
SUB test ’ Original test kept for reference (now calls DirWindow$)
LOCAL result$
CLS
result$ = DirWindow$(0, 0, 24, “B:/”, “”)
PRINT @(0, 0) result$;
END SUB
'-------------
'-------------------------------------------------------------------------------
’ FUNCTION DirWindow$ (x, y, lines, initialPath$, fileExtensionFilter$)
’ Displays a file/directory browser window.
’ Parameters:
’ x, y: Top-left coordinates of the window.
’ lines: Number of lines to display in the file list area.
’ initialPath$: Starting directory path.
’ fileExtensionFilter$: Optional file extension filter (e.g., “TXT”, “JPG”).
’ Returns:
’ A string containing “Path$SelectedItemName$” or “999” if cancelled.
'-------------------------------------------------------------------------------
FUNCTION DirWindow$(x, y, lines, initialPath$, fileExtensionFilter$)
CONST MAX_FILES_DISPLAY = 256 ’ Max number of files/dirs to display
CONST PATH_DISPLAY_TRUNCATE_LENGTH = DIR_LIST_WIDTH - 15 ’ Space for path + “[D] Drive”
LOCAL fileNames$(MAX_FILES_DISPLAY)
LOCAL inputChar$, i, n, currentFile$, searchPattern$
LOCAL currentPath$, fileCount, topVisibleLine, bottomVisibleLine
LOCAL firstLineToDisplay, x2, y2, y3, y4, y5
LOCAL boxWidth, boxHeight, selectedItemName$, cancelledSignal$
LOCAL cursorIndex, displayMode, exitState
LOCAL sortOrder AS INTEGER ' Renamed dflags to sortOrder
LOCAL filterText$ ' For the interactive filter
DirWindow$ = "999" ' Default return value for cancellation
exitState = 1 ' Initial state: load directory
sortOrder = SORT_MODE_DIRS_FIRST ' Default sort order (directories first)
filterText$ = fileExtensionFilter$ ' Initialize filter with passed value
' --- Calculate UI dimensions ---
boxWidth = fontw * DIR_LIST_WIDTH + 6
boxHeight = fonth * lines + 3
x2 = x + 3
y2 = y + fonth + 3
y3 = y + 2
y4 = y2 + 3
y5 = y2 + boxHeight + 2
' --- Draw UI Boxes ---
BOX x, y, boxWidth, fonth + 3, 1, COLOR_BOX_BORDER ' Path display box
BOX x, y + fonth + 4, boxWidth, boxHeight, 1, COLOR_BACKGROUND_LIST ' File list box
BOX x, y5, boxWidth, fonth + 3, 1, COLOR_BACKGROUND_LIST ' Command bar box
' --- Draw command labels and separators ---
PRINT @(fontw * (DIR_LIST_WIDTH - 9), y3) "[D] Drive"
LINE fontw * (DIR_LIST_WIDTH - 9) - 4, y, fontw * (DIR_LIST_WIDTH - 9) - 4, fonth + 3, 1, COLOR_BOX_BORDER
PRINT @(x + 3, y5 + 2) "[S]ort [F]ilter [H]ome [E]nd [PgUp] [PgDn] [Esc] Exit"
' --- Set search pattern ---
IF filterText$ = "" THEN
searchPattern$ = "*"
ELSE
' Ensure filter is uppercase for comparison
searchPattern$ = UCASE$(filterText$)
IF INSTR(1, searchPattern$, "*") = 0 AND INSTR(1, searchPattern$, ".") = 0 THEN
searchPattern$ = "*." + searchPattern$ ' Assume it's an extension if no wildcard/dot
END IF
END IF
' --- Set initial path ---
IF initialPath$ <> "" THEN
currentPath$ = initialPath$
ELSE
currentPath$ = CWD$ ' Current working directory
END IF
' Try to change to initial path, handle error
ON ERROR GOTO HandlePathError
CHDIR currentPath$
ON ERROR GOTO 0 ' Reset error handling
'---------------------------------'
' Main Loop
'---------------------------------'
DO
' --- Load dir array if state requires it ---
IF exitState = 1 THEN ' Full redraw and directory re-scan
fileCount = 0
displayMode = 0
cursorIndex = 1
firstLineToDisplay = 1
' Clear previous list display before loading new one
FOR i = 0 TO lines - 1
PRINT @(x2, y4 + i * fonth, 0) SPACE$(DIR_LIST_WIDTH);
NEXT i
' Add ".." entry if not at root (assuming X:\ is 3 chars)
IF LEN(currentPath$) > 3 THEN
fileCount = 1
fileNames$(1) = DIR_ENTRY_PREFIX + ".."
END IF
' Get directories
ON ERROR RESUME NEXT ' Temporarily ignore errors during Dir$
currentFile$ = DIR$("*", DIR)
DO WHILE currentFile$ <> "" AND fileCount < MAX_FILES_DISPLAY
INC fileCount, 1
fileNames$(fileCount) = DIR_ENTRY_PREFIX + currentFile$
currentFile$ = DIR$()
LOOP
' Get files
currentFile$ = DIR$(searchPattern$, FILE)
DO WHILE currentFile$ <> "" AND fileCount < MAX_FILES_DISPLAY
INC fileCount, 1
fileNames$(fileCount) = FILE_ENTRY_PREFIX + currentFile$
currentFile$ = DIR$()
LOOP
ON ERROR GOTO 0 ' Reset error handling
' Sort the file list
SORT fileNames$(),, sortOrder, 1, fileCount
' Display current path and file count
LOCAL displayPath$
IF LEN(currentPath$) > PATH_DISPLAY_TRUNCATE_LENGTH THEN
displayPath$ = LEFT$(currentPath$, 2) + "..." + RIGHT$(currentPath$, PATH_DISPLAY_TRUNCATE_LENGTH - 5)
ELSE
displayPath$ = LEFT$(currentPath$ + SPACE$(PATH_DISPLAY_TRUNCATE_LENGTH), PATH_DISPLAY_TRUNCATE_LENGTH)
END IF
PRINT @(x2, y3) displayPath$;
PRINT @(x + boxWidth - fontw * 10, y3) "Files:" + LTRIM$(STR$(fileCount)); ' Display file count
END IF
'---------- Display directory contents ----------
DO
SELECT CASE exitState
CASE 1, 2 ' Full redraw or simple cursor move/scroll
topVisibleLine = firstLineToDisplay
bottomVisibleLine = firstLineToDisplay + lines - 1
CASE 3 ' Cursor moved up and list scrolled up (handled by cursor moving to top of screen)
topVisibleLine = firstLineToDisplay
bottomVisibleLine = firstLineToDisplay + lines - 1
CASE 4 ' Cursor moved down and list scrolled down (handled by cursor moving to bottom of screen)
topVisibleLine = firstLineToDisplay
bottomVisibleLine = firstLineToDisplay + lines - 1
END SELECT
FOR i = topVisibleLine TO bottomVisibleLine
LOCAL displayFileName$, itemType$
IF i <= fileCount THEN
LOCAL rawFileName$, fileNameLength
rawFileName$ = RIGHT$(fileNames$(i), LEN(fileNames$(i)) - 1)
fileNameLength = LEN(rawFileName$)
displayFileName$ = LEFT$(rawFileName$ + SPACE$(DIR_LIST_WIDTH - 4), DIR_LIST_WIDTH - 4)
IF LEFT$(fileNames$(i), 1) = DIR_ENTRY_PREFIX THEN
itemType$ = "DIR "
ELSE
itemType$ = "FILE"
END IF
displayFileName$ = displayFileName$ + itemType$
IF i = firstLineToDisplay - 1 + cursorIndex THEN
displayMode = 2 ' Highlight selected item
ELSE
displayMode = 0 ' Normal text
END IF
ELSE
displayFileName$ = SPACE$(DIR_LIST_WIDTH) ' Blank line
displayMode = 0
END IF
PRINT @(x2, y4 + (i - firstLineToDisplay) * fonth, displayMode) displayFileName$
NEXT i
' --- Wait for user input ---
DO : inputChar$ = INKEY$: LOOP UNTIL inputChar$ <> ""
LOCAL asciiValue AS INTEGER = ASC(UCASE$(inputChar$)) ' UCASE for commands
exitState = 0 ' Reset state, assume no major change
' --- Handle user input ---
SELECT CASE asciiValue
CASE KEY_UP ' Up Arrow
IF cursorIndex > 1 THEN
INC cursorIndex, -1
exitState = 2 ' Cursor moved, partial redraw
ELSEIF firstLineToDisplay > 1 THEN
INC firstLineToDisplay, -1
exitState = 2 ' Scrolled up, full list redraw
END IF
CASE KEY_DOWN ' Down Arrow
IF (cursorIndex + firstLineToDisplay - 1) < fileCount THEN
IF cursorIndex < lines THEN
INC cursorIndex, 1
exitState = 2 ' Cursor moved, partial redraw
ELSEIF (firstLineToDisplay + lines - 1) < fileCount THEN
INC firstLineToDisplay, 1
exitState = 2 ' Scrolled down, full list redraw
END IF
END IF
CASE KEY_PAGE_UP ' Page Up
IF firstLineToDisplay > 1 THEN
firstLineToDisplay = MAX(1, firstLineToDisplay - lines)
cursorIndex = 1 ' Reset cursor to top of page
exitState = 2
END IF
CASE KEY_PAGE_DOWN ' Page Down
IF (firstLineToDisplay + lines - 1) < fileCount THEN
firstLineToDisplay = MIN(fileCount - lines + 1, firstLineToDisplay + lines)
IF firstLineToDisplay < 1 THEN firstLineToDisplay = 1 ' Ensure it doesn't go negative
cursorIndex = 1 ' Reset cursor to top of page
exitState = 2
END IF
CASE KEY_HOME ' Home
firstLineToDisplay = 1
cursorIndex = 1
exitState = 2
CASE KEY_END ' End
firstLineToDisplay = MAX(1, fileCount - lines + 1)
cursorIndex = fileCount - firstLineToDisplay + 1
exitState = 2
CASE KEY_ENTER ' Enter
selectedItemName$ = RIGHT$(fileNames$(cursorIndex + firstLineToDisplay - 1), LEN(fileNames$(cursorIndex + firstLineToDisplay - 1)) - 1)
IF LEFT$(fileNames$(cursorIndex + firstLineToDisplay - 1), 1) = DIR_ENTRY_PREFIX THEN ' It's a directory
exitState = 1 ' Re-scan directory
IF selectedItemName$ = ".." THEN
ON ERROR GOTO HandlePathError
CHDIR ".."
currentPath$ = CWD$
ON ERROR GOTO 0
ELSE
IF RIGHT$(currentPath$, 1) <> "/" AND RIGHT$(currentPath$, 1) <> "\" THEN
currentPath$ = currentPath$ + "/" + selectedItemName$
ELSE
currentPath$ = currentPath$ + selectedItemName$
END IF
ON ERROR GOTO HandlePathError
CHDIR currentPath$
ON ERROR GOTO 0
END IF
ELSE ' It's a file
exitState = 5 ' Exit with file selected
END IF
CASE ASC("D") ' Change Drive
exitState = 1
LOCAL newDrive$
IF LEFT$(currentPath$, 1) = "A" THEN
newDrive$ = "B:"
ELSE
newDrive$ = "A:"
END IF
ON ERROR GOTO HandlePathError
DRIVE newDrive$
currentPath$ = CWD$
ON ERROR GOTO 0
CASE ASC("S") ' Sort Toggle
exitState = 1
IF sortOrder = SORT_MODE_DIRS_FIRST THEN
sortOrder = SORT_MODE_ALL_ALPHA
ELSE
sortOrder = SORT_MODE_DIRS_FIRST
END IF
CASE ASC("F") ' Filter
LOCAL newFilterInput$
newFilterInput$ = TextInput$(x + 3, y5 + 2, "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.-_*", 10)
IF newFilterInput$ <> "" THEN
filterText$ = newFilterInput$ ' Update the filter
ELSE
filterText$ = "" ' Clear filter if empty input
END IF
' Re-draw command bar to clear input box
PRINT @(x + 3, y5 + 2) LEFT$("[S]ort [F]ilter [H]ome [E]nd [PgUp] [PgDn] [Esc] Exit" + SPACE$(DIR_LIST_WIDTH), DIR_LIST_WIDTH);
exitState = 1 ' Re-scan after filter change
CASE KEY_ESCAPE ' Escape
exitState = 6
selectedItemName$ = "" ' Clear selection for cancellation
END SELECT
LOOP UNTIL exitState > 0 ' Loop until a significant state change (e.g., directory change, file selection, exit)
LOOP UNTIL exitState > 4 ' Loop until selected file (5) or cancelled (6)
DirWindow$ = currentPath$ + "$" + selectedItemName$ ' Return path and selected item
EXIT FUNCTION ' Exit function normally
HandlePathError:
PRINT @(x2, y3) “Error: Access Denied or Invalid Path!” + SPACE$(LEN(currentPath$)) ’ Overwrite path with error msg
currentPath$ = “A:/” ’ Attempt to fallback to A:
DRIVE “A:”
currentPath$ = CWD$
RESUME NEXT ’ Continue execution after error
END FUNCTION
'-------------------------------------------------------------------------------
’ FUNCTION TextInput$ (x, y, allowedKeys$, maxLength)
’ Displays a single-line text input field.
’ Parameters:
’ x, y: Top-left coordinates of the input field.
’ allowedKeys$: String of characters allowed for input.
’ maxLength: Maximum length of the input string.
’ Returns:
’ The string entered by the user.
'-------------------------------------------------------------------------------
FUNCTION TextInput$(x, y, allowedKeys$, maxLength)
LOCAL blinkTimer, currentText$, cursorCol, inputChar$, charAtCursor$, displayMode
blinkTimer = 0
currentText$ = ""
cursorCol = 0 ' Represents the column position of the cursor (0-based)
' Clear the display area for the input field
PRINT @(x, y) SPACE$(maxLength);
DO
DO
inputChar$ = INKEY$
blinkTimer = (blinkTimer + 1) MOD 1000
IF blinkTimer < 500 THEN displayMode = 2 ELSE displayMode = 0 ' Blinking cursor mode
' Display blinking cursor at current position
IF cursorCol < LEN(currentText$) THEN
charAtCursor$ = MID$(currentText$, cursorCol + 1, 1)
ELSE
charAtCursor$ = " " ' Blank space if cursor at end
END IF
PRINT @(x + (cursorCol * fontw), y, displayMode) charAtCursor$;
LOOP WHILE inputChar$ = "" ' Wait for a key press
' Reset character at cursor to normal display before processing new input
IF cursorCol < LEN(currentText$) THEN
PRINT @(x + (cursorCol * fontw), y, 0) MID$(currentText$, cursorCol + 1, 1);
ELSE
PRINT @(x + (cursorCol * fontw), y, 0) " ";
END IF
LOCAL asciiVal AS INTEGER = ASC(inputChar$) ' Do not UCASE here, allow case-sensitive input
SELECT CASE asciiVal
CASE KEY_BACKSPACE
IF cursorCol > 0 THEN
currentText$ = LEFT$(currentText$, cursorCol - 1) + RIGHT$(currentText$, LEN(currentText$) - cursorCol)
cursorCol = cursorCol - 1
PRINT @(x, y) LEFT$(currentText$ + SPACE$(maxLength), maxLength); ' Redraw the full string
END IF
CASE KEY_ENTER
EXIT DO ' User pressed Enter, finish input
' Assuming 130 and 131 are Left and Right arrow keys - VERIFY THESE!
CASE 130 ' KEY_LEFT (example, verify ASCII for your environment)
IF cursorCol > 0 THEN
cursorCol = cursorCol - 1
END IF
CASE 131 ' KEY_RIGHT (example, verify ASCII for your environment)
IF cursorCol < LEN(currentText$) THEN
cursorCol = cursorCol + 1
END IF
CASE ELSE
IF INSTR(1, allowedKeys$, inputChar$) > 0 AND LEN(currentText$) < maxLength THEN
' Insert character at cursor position
currentText$ = LEFT$(currentText$, cursorCol) + inputChar$ + RIGHT$(currentText$, LEN(currentText$) - cursorCol)
cursorCol = cursorCol + 1
PRINT @(x, y) LEFT$(currentText$ + SPACE$(maxLength), maxLength); ' Redraw the full string
END IF
END SELECT
LOOP
TextInput$ = currentText$
END FUNCTION