Notice. New forum software under development. It's going to miss a few functions and look a bit ugly for a while, but I'm working on it full time now as the old forum was too unstable. Couple days, all good. If you notice any issues, please contact me.
|
Forum Index : Microcontroller and PC projects : WIP CMM2 Gen 2: A bit of fun - CMM2 File Manager
Page 1 of 2 | |||||
Author | Message | ||||
PeteCotton Guru Joined: 13/08/2020 Location: CanadaPosts: 367 |
I know - The CMM2 already has a fully working file manager. But this started as a bit of fun. It was originally going to be a GUI for a graphical text adventure like the old Magnetic Scrolls "The Pawn" or "Guild of Thieves". Then it sort of morphed into a front end for launching programs or managing files. It's a work in progress, so it almost certainly has a few bugs, and there's a lot more features I want to add to it (Ram Disk, Networking etc.), but I want to spend time on some games dev, so it's going back on the shelf for the time being. It happily handles files and directories being moved, renamed. Launching programs. Copying links for folders to the desktop (for easier launching) and it has a Rubbish bin for deleted files, so that they can be recovered. It does require a CMM2 Gen 2 as it uses the mouse. It is by no means intended to be an operating system - I 100% still prefer that the CMM2 boots straight to BASIC and not to an OS. But part of my thinking was that if you did want to make a system that was easier for the grandkids to launch games etc. then this would suffice. It would be simple to disable the disruptive commands (Delete, Move, rename etc.) so that it was only a file launcher. Video of it in action here: https://1drv.ms/v/s!AnkZn9N9Pt1fyMISsNqOildOm0r6jA?e=cCDuMg Download source here: https://1drv.ms/u/s!AnkZn9N9Pt1fyMIRhgu0qZTfhJjlIQ?e=dw0vZn |
||||
PeteCotton Guru Joined: 13/08/2020 Location: CanadaPosts: 367 |
Here's the source if you don't want to download the .zip file. OPTION EXPLICIT ' This program is split in to the following sections ' Variable Initialisation ' Main Loop ' Mouse Handling (C1_xxxx) ' Main Screen Graphics (C2_xxxx) ' Window Graphics (C3_xxxx) ' Window Logic (C4_xxxx) ' Window Contents Array (C5_xxxx) ' Text Input Window (C6_xxxx) ' Menu Graphics and Logic (C7_xxxx) ' Generic Graphics (C8_xxxx) ' File Handling (C9_xxxx) '******************************************************************************************** ' VARIABLE INITIALISATION '******************************************************************************************** CONST FOREGROUND1=RGB(WHITE) CONST BACKGROUND1=RGB(40,40,80) ' Nice dark blue for desktop CONST BACKGROUND2=RGB(87,88,90) ' Menu Bar CONST BACKGROUND3=RGB(80,80,160) ' background of windows CONST MENUBACKGROUND1=RGB(67,68,183) CONST MENUBACKGROUND2=RGB(47,48,100) CONST MENUFOREGROUND1=RGB(255,255,255) DIM INTEGER MouseX%, MouseY%, MouseLeft%, MouseRight%, MouseDx%, MouseDy%, LastMouseX%, LastMouseY%, LastMouseLeft% DIM INTEGER WindowClickedOn%, WindowSizeChanging%, ScrollbarDragging% DIM INTEGER SelectedDesktopIcon% DIM INTEGER I%, Id%, Wid% DIM INTEGER SystemMenuOpenState% DIM STRING OriginalWorkingDirectory$ ' Used to remember what the working directory was on startup ' Upto 100 icons and windows CONST WIN_ID=0 CONST WIN_X=1 CONST WIN_Y=2 CONST WIN_W=3 CONST WIN_H=4 CONST WIN_TYPE=5 CONST WIN_FIRSTICON=6 CONST WIN_SCROLLBARTOP=7 CONST WIN_ICONCOUNT=8 DIM INTEGER IconData%(100,6) CONST ICON_ID=0 CONST ICON_X=1 CONST ICON_Y=2 CONST ICON_W=3 CONST ICON_H=4 CONST ICON_STATUS=5 'status is 0 - None, 1 - Open DIM STRING IconName$(100) DIM INTEGER WindowData%(100,9) ' Data is saved as IconId,x,y,w,h,type where type is 1 = DriveFolder DIM STRING WindowPaths$(100) ' Path for each drive folder window DIM STRING WindowContents$(100,10) DIM INTEGER SelectedIconsInTopWindow%(1000) DIM INTEGER WindowLeft%, WindowRight%, WindowTop%, WindowBottom%, WindowHeight%, WindowWidth% DIM STRING DebugLine$, DebugLine2$ DIM INTEGER DblClk_TimeSinceLastClick%, DblClk_LastClickedIndex% DIM STRING FilenamesToCopy$(1000), PathToCopyFrom$ DIM STRING GUIMessage$ DIM STRING GUIMenuOptions$(20) DIM INTEGER GUIMenuOptionsParameters%(100) 'id,x,y,width,height of each button DIM INTEGER GUIMenuParameters%(5) ' Menu id (0 is no menu), x,y,width,height DIM INTEGER GUIMenuCurrentItem% ' The item the mouse is hovering over (-1 for none) DIM INTEGER ScrollTop%, ScrollBottom% DIM STRING SDCardStatus$ DIM INTEGER SDCardOK% DIM INTEGER TextInputWindowType%=0 ' 0 - None, 1- Rename Directory or File, 2-New Directory DIM STRING TextInputWindowOriginal$ ' The original name of the file or directory to be renamed DIM STRING TextInputWindowContents$ ' The new name for the file or directory (user input) DIM INTEGER Key% ' Stores keycode of last key pressed DIM INTEGER NeedToRefreshScreenFlag% ' If 1, then the screen will be redrawn DIM INTEGER KeyDebounce% DIM INTEGER EnterPressed%=0 ' Remembers if Enter pressed on TextInputWindow DIM INTEGER EscapePressed%=0 ' Remembers if Escape pressed on TextInputWindow CONST MENU_ID=0 CONST MENU_X=1 CONST MENU_Y=2 CONST MENU_W=3 CONST MENU_H=4 '******************************************************************************************** ' MAIN LOOP '******************************************************************************************** OriginalWorkingDirectory$=CWD$ ' Remember the Working Directory so we can restore it on exit ON ERROR SKIP 1 ' If the mouse isn't initialised the following line will throw an error CONTROLLER MOUSE OPEN 0,C1_LeftButton,C1_RightButton IF MM.ERRNO=16 THEN PRINT "There was a problem initialising the mouse. Make sure a PS2 compatible mouse is plugged in." PRINT "You may need to eneter the command 'OPTION MOUSE 0' to enable the mouse." END END IF 'MODE 10,8 '848x480 - Doesn't work well on my Asus monitor MODE 11,8 ' 1280x720 FONT 7 PAGE DISPLAY 0 GUI CURSOR ON 0,0,0,FOREGROUND1 GUI CURSOR LINK MOUSE PAGE WRITE 2 LOAD PNG "A:\GUI\img\icons2",0,0 PAGE WRITE 0 FOR I%=0 TO 99 IconData%(I%,ICON_ID)=0:IconData%(I%,ICON_X)=0:IconData%(I%,ICON_Y)=0:IconData%(I%,ICON_W)=0 IconData%(I%,ICON_H)=0:IconData%(I%,ICON_STATUS)=0 NEXT I% FOR I% = 0 TO 99 WindowData%(I%,WIN_Id)=0:WindowData%(I%,WIN_X)=0:WindowData%(I%,WIN_Y)=0 WindowData%(I%,WIN_W)=0:WindowData%(I%,WIN_H)=0:WindowData%(I%,WIN_TYPE)=0 WindowData%(I%,WIN_FIRSTICON)=0:WindowData%(I%,WIN_SCROLLBARTOP)=0:WindowData%(I%,WIN_ICONCOUNT)=0 NEXT I% C2_RedrawScreen C9_CopyGUIFilesToDisk ' Make sure Rubbish directory exists DO ' Read the mouse MouseX% = MOUSE(x,0) MouseY% = MOUSE(y,0) MouseLeft% = MOUSE(l,0) MouseRight% = MOUSE(r,0) MouseDx% = MouseX% - LastMouseX% ' Calculate how much the Mouse X has moved since the last scan MouseDy% = MouseY% - LastMouseY% ' Calculate how much the Mouse Y has moved since the last scan LastMouseX% = MouseX%:LastMouseY% = MouseY% IF MouseLeft% = 1 AND LastMouseLeft% = 1 THEN IF MouseDx% <> 0 OR MouseDy% <> 0 THEN ' We are dragging the mouse IF WindowClickedOn% <> -1 THEN Wid%=WindowClickedOn% ' TODO Wid is always 0??? IF ScrollbarDragging% = 1 THEN ' Scrollbar is being dragged ScrollBottom% = WindowData%(Wid,WIN_H)-32 WindowData%(Wid%,WIN_SCROLLBARTOP) = WindowData%(wid,WIN_SCROLLBARTOP) + MouseDy% WindowData%(Wid%,WIN_ICONCOUNT) = -1 ' Force the icons to be recalculated IF WindowData%(Wid%,WIN_SCROLLBARTOP) < 0 THEN WindowData%(Wid%,WIN_SCROLLBARTOP) = 0 IF WindowData%(Wid%,WIN_SCROLLBARTOP) > ScrollBottom% THEN WindowData%(Wid%,WIN_SCROLLBARTOP) = ScrollBottom% ELSE ' Window is being dragged WindowData%(Wid%,WIN_X) = WindowData%(Wid%,WIN_X) + MouseDx% WindowData%(Wid%,WIN_Y) = WindowData%(Wid%,WIN_Y) + MouseDy% END IF C2_RedrawScreen END IF IF WindowSizeChanging% <> -1 THEN ' Window is being resized. It is always the topmost window WindowData%(0,WIN_W) = WindowData%(0,WIN_W) + MouseDx% WindowData%(0,WIN_H) = WindowData%(0,WIN_H) + MouseDy% WindowData%(Wid%,WIN_ICONCOUNT) = -1 ' Force the icons to be recalculated IF WindowData%(Wid%,WIN_W) < 100 THEN WindowData%(Wid%,WIN_W) = 100 ' Minimum window width of 100 IF WIndowData%(Wid%,WIN_H) < 100 THEN WindowData%(Wid%,WIN_H) = 100 ' Minimum window height of 100 C2_RedrawScreen END IF END IF END IF IF mouseLeft% = 0 THEN WindowClickedOn% = -1:WindowSizeChanging% = -1:ScrollBarDragging% = -1 lastMouseLeft% = mouseLeft% C7_RefreshGUIMenu IF KEYDOWN(0) = 0 THEN KeyDebounce% = 1 FOR I% = 1 TO KEYDOWN(0) Key% = KEYDOWN(I%) IF TextInputWindowType% <> 0 THEN ' The user is typing in the new name for a file or folder IF KeyDebounce% = 1 THEN NeedToRefreshScreenFlag% = 1 IF key% = 8 AND LEN(TextInputWindowContents$) > 0 THEN ' Backspace TextInputWindowContents$ = MID$(TextInputWindowContents$,1,LEN(TextInputWindowContents$)-1) ENDIF IF Key% > 32 AND Key% < 127 AND LEN(TextInputWindowContents$) < 32 THEN TextInputWindowContents$ = TextInputWindowContents$ + CHR$(key%) ENDIF IF Key% = 10 OR Key% = 13 THEN ' Enter key pressed EnterPressed% = 1 C6_ProcessTextInputWindow ENDIF IF Key% = 27 THEN EscapePressed% = 1 C6_ProcessTextInputWindow ENDIF KeyDebounce% = 0 ENDIF ELSE IF Key%=67 OR Key%=99 THEN ' C is 67 or 99 (upper/lower case) C9_CopyButtonPressed END IF IF Key% = 80 OR Key% = 112 THEN ' P is 80 or 112 (upper/lower case) C9_PasteButtonPressed END IF END IF NEXT I% C9_ReadInsertedSDCard IF NeedToRefreshScreenFlag% THEN C2_RedrawScreen NeedToRefreshScreenFlag% = 0 LOOP '******************************************************************************************** ' MOUSE HANDLING C1_xxxx '******************************************************************************************** SUB C1_LeftButton ' Interupt on left button click C1_HandleMouseClick END SUB SUB C1_RightButton ' Interupt on right button click C1_HandleMouseClick END SUB SUB C1_HandleMouseClick STATIC INTEGER IgnoreInterrupt%=0 ' Important that this is a static STATIC INTEGER IconsPerRow% STATIC INTEGER LocalIconId%, RowCalc% STATIC INTEGER debounceTimer% LOCAL INTEGER x%,y%,w% IF Timer - debounceTimer% < 500 THEN EXIT SUB ' Clicking on a menu disables the click event for half a second IF IgnoreInterrupt% = 1 THEN EXIT SUB IgnoreInterrupt% = 1 ' Make sure this interrupt doesn't fire again until it is finished processing the last call MouseX% = MOUSE(X,0) MouseY% = MOUSE(Y,0) MouseLeft% = MOUSE(L,0) MouseRight% = MOUSE(R,0) ' The TextInputWindow is modal - i.e. all other clicks are ignored until it is closed IF TextInputWindowType% <> 0 THEN C6_ProcessTextInputWindow IgnoreInterrupt% = 0 ' Re-enable this interrupt EXIT SUB END IF C7_RefreshGUIMenu 'Check if it's a menu click (menu's are topmost if there is no TextInputWindow) IF GuiMenuCurrentItem% <> -1 THEN DebounceTimer% = Timer ' To prevent unwanted clicks - clicking on a menu disables the mouse click for half a second IF GUIMenuParameters%(MENU_ID) = 1 THEN ' File copy Menu -Copy,Paste,Make Dir and Delete IF GuiMenuCurrentItem% = 0 THEN ' Copy C9_CopyButtonPressed IgnoreInterrupt% = 0:C7_CloseMenu:EXIT SUB ' The click has been handled - no need to go further END IF IF GuiMenuCurrentItem% = 1 AND PathToCopyFrom$ <> WindowPaths$(0) THEN ' Paste - Check we're not pasting back to the source directory C9_PasteButtonPressed IgnoreInterrupt% = 0:C7_CloseMenu:EXIT SUB ' The click has been handled - no need to go further ENDIF IF GuiMenuCurrentItem% = 2 THEN ' Move C9_MoveSelectedFiles WindowPaths(0) IgnoreInterrupt% = 0:C7_CloseMenu:EXIT SUB ' The click has been handled - no need to go further ENDIF IF GuiMenuCurrentItem% = 3 THEN ' Make Directory C6_OpenTextInputWindowAsNewDir ENDIF IF GuiMenuCurrentItem% = 4 THEN ' Link to Desktop C9_AddSelectedIconsToDesktop ENDIF IF GuiMenuCurrentItem% = 6 THEN ' Rename C6_OpenTextInputWindowAsRename ENDIF IF GuiMenuCurrentItem% = 7 THEN ' Delete C9_DeleteSelectedFiles IgnoreInterrupt% = 0:C7_CloseMenu:EXIT SUB ' The click has been handled - no need to go further ENDIF END IF IF GUIMenuParameters%(MENU_ID) = 2 THEN ' Desktop icon menu - Remove from desktop IF GuiMenuCurrentItem% = 0 THEN ' Remove from desktop C9_RemoveSelectedIconFromDesktop END IF END IF ENDIF C7_CloseMenu ' If we are here, then the user clicked somewhere on the screen. If a menu was open, then close it DebugLine2$ = "" IF MouseY% < 15 THEN ' Top of screen Menu Bar Click IF MouseX% < 40 THEN ' System Menu Open/Close C7_SystemMenuOpenClose END IF IF MouseX% > MM.HRES - 20 THEN ' Close program IF MM.INFO(EXISTS DIR OriginalWorkingDirectory$)=1 THEN CHDIR OriginalWorkingDirectory$ ' Remember to put the user back in the working directory they started from END IF CLS END END IF ELSE 'Check if this was a window the user clicked on FOR I% = 0 TO 99 IF WindowData%(I%,WIN_ID) <> 0 THEN WindowLeft% = WindowData%(I%,WIN_X):WindowRight% = WindowData%(I%,WIN_X) + WindowData%(I%,WIN_W) WindowTop% = WindowData%(I%,WIN_Y):WindowBottom% = WIndowData%(I%,WIN_Y) + WindowData%(I%,WIN_H) WindowHeight% = WindowTop% - WindowBottom%:WindowWidth% = WindowRight% - WindowLeft% IF MouseY% > WindowTop% AND MouseY% < WindowBottom% THEN IF MouseX% > WindowLeft% AND MouseX% < WindowRight% THEN DebugLine2$ = "Window Found" IF I% <> 0 THEN ' Is this window topmost - if not make it topmost (Move to position 0) C4_MoveWindowToTop I% C4_ClearSelectedIcons ' Unselect and selected icons - i.e. no icons are selected END IF WindowClickedOn% = 0 ' Topmost window clicked on (0 is always the topmost) ' Is it the windows resize icon IF MouseX% > WindowRight% - 15 AND MouseY% > WindowBottom% - 15 THEN WindowSizeChanging% = 0 WindowClickedOn% = -1 ELSEIF MouseX% < WindowLeft% + 40 AND MouseY% < WindowTop% + 15 THEN 'Did the user click on the back button? C4_ClearSelectedIcons ' Unselect and selected icons - i.e. no icons are selected WindowPaths$(0) = C9_GetParentPath(WindowPaths$(0)) ' Move to the parent folder C5_UpdateWindowContents 0 ' Read the contents of the new folder from disk ELSEIF MouseX% > WindowRight% - 20 AND MouseY% < WindowTop% + 15 THEN 'Did the user just click on the close button C4_CloseWindowWithIconId(WindowData%(0,WIN_ID)) ' The active window is always 0 - close it WindowClickedOn% = -1 ' No window selected ELSEIF MouseX% > WindowRight% - 10 AND MouseY% > WindowTop% + 16 AND MouseY% < WindowBottom% - 22 THEN ' User has clicked on the scrollbar ScrollbarDragging% = 1 ELSEIF MouseY% > WindowTop% + 15 THEN ' They're not clicking on the window toolbar ' Check and see if they clicked on a file or folder IconsPerRow% = (WindowWidth% - 4 - 30)/60 ' We subtract 1 for cases where the width is an exact multiple of 60. And the 30 is because basic rounds up IF MouseX% - WindowLeft% < IconsPerRow% * 60 THEN ' Check they are actually in the icon area LocalIconId% = (MouseX% - WindowLeft% + 4 - 30)/60 ' Calculate the column RowCalc% = (MouseY% - WindowTop% - 35)/50 LocalIconId% = LocalIconId% + (RowCalc% * IconsPerRow%) + WindowData%(0, WIN_FIRSTICON) IF MouseLeft% = 1 THEN ' This is a left mouse click IF TIMER - DblClk_TimeSinceLastClick% < 500 AND DblClk_LastClickedIndex% = LocalIconId% THEN ' This is a double click event C1_DoubleClickEvent 0, LocalIconId% ELSE C4_AddIconToSelectedIcons LocalIconId%, 1 ' The 1 at the end allows for de-selection END IF DblClk_LastClickedIndex% = LocalIconId% DblClk_TimeSinceLastClick% = TIMER END IF IF MouseRight% = 1 THEN IF PathToCopyFrom$ = WindowPaths$(0) OR PathToCopyFrom$ = "" THEN ' If the user is still on the window they are copying from, then a right ' click will select the icon under the mouse C4_AddIconToSelectedIcons LocalIconId%, 0 ENDIF END IF END IF ' If we are here, then the user has clicked on a window IF MouseRight% = 1 THEN C7_SetMenu 1,"Copy","Paste","Move","Make Dir","Put on Desktop","-----","Rename","Delete" END IF END IF C2_RedrawScreen IgnoreInterrupt% = 0 ' Re-enable this interrupt EXIT SUB END IF END IF END IF NEXT I% 'Check if it the user clicked on a desktop icon FOR I% = 0 TO 99 IF IconData%(I%,ICON_ID) = 0 THEN EXIT FOR IF MouseY% > IconData%(I%,ICON_Y) AND MouseY% < IconData%(I%,ICON_Y) + IconData%(I%,ICON_H) THEN IF MouseX% > IconData%(I%,ICON_X) AND MouseX% < IconData%(I%,ICON_X) + IconData%(I%, ICON_W) THEN IF MouseRight% = 1 THEN SelectedDesktopIcon%=I% C7_SetMenu 2,"Remove from desktop" ELSE IF TIMER - DblClk_TimeSinceLastClick% < 500 AND DblClk_LastClickedIndex% = LocalIconId% THEN ' This is a double click event ' It's this icon IF IconData%(I%,ICON_STATUS)=0 THEN IconData%(I%,ICON_STATUS)=1 ' Open Window for this icon ' Find next available Window Id Wid% = C4_NextAvailableWindowId() WindowData%(Wid%, WIN_ID) = IconData%(I%,ICON_ID) WindowData%(Wid%, WIN_X) = IconData%(I%,ICON_X) + 20 WindowData%(Wid%, WIN_Y) = IconData%(I%,ICON_Y) + 20 WindowData%(Wid%, WIN_W) = 300 ' Set width WindowData%(Wid%, WIN_H) = 200 ' Set height WindowData%(Wid%, WIN_TYPE) = 1 ' Folder type WindowData%(Wid%, WIN_FIRSTICON) = 0 WindowData%(Wid%, WIN_SCROLLBARTOP) = 0 WindowData%(Wid%, WIN_ICONCOUNT) = -1 ' Force Icons to be recalculated WindowPaths$(Wid%) = IconName$(I) C5_UpdateWindowContents Wid% C4_MoveWindowToTop Wid% C4_ClearSelectedIcons ' If there are icons selected on another window - unselect then ELSE ' Close window for this icon C4_CloseWindowWithIconId(IconData%(I%,ICON_ID)) END IF C2_RedrawScreen EXIT FOR ENDIF DblClk_TimeSinceLastClick = TIMER END IF END IF NEXT I END IF IgnoreInterrupt% = 0 ' Re-enable this interrupt END SUB SUB C1_DoubleClickEvent winId%, iconId% LOCAL STRING filename$ filename$ = C5_GetItemWinContentsArray(winId%, iconId%) IF LEFT$(filename$,1)="D" THEN ' This is a directory - so drill into it C4_ClearSelectedIcons ' Moving directories de-selects any selected icons WindowPaths$(winId%)=WindowPaths$(winId%) + "\" + MID$(filename$,2) C5_UpdateWindowContents winId% ELSE ' Logic for double clicking on a file goes here IF LCASE$(RIGHT$(filename$,4))=".bas" THEN EXECUTE "RUN " + CHR$(34) + WindowPaths$(winId%) + "\" + MID$(filename$,2) + CHR$(34) END IF END IF END SUB '******************************************************************************************** ' MAIN SCREEN GRAPHICS C2_xxxx '******************************************************************************************** SUB C2_RedrawScreen LOCAL INTEGER x%,y%,w%,d% PAGE WRITE 1 CLS BACKGROUND1 BOX 0,0,MM.HRES,15,1,RGB(BLACK),BACKGROUND2 TEXT 5,4,"Menu",,,,FOREGROUND1,BACKGROUND2 TEXT MM.HRES-10,4,"X",,,,FOREGROUND1,BACKGROUND2 DebugLine$="Mouse:" + STR$(mouseX)+","+STR$(mouseY)+",DX" + STR$(mouseDx)+",DY"+STR$(mouseDy)+",L"+STR$(mouseleft)+ " " +STR$(WindowData(0,WIN_X))+","+STR$(WindowData(0,WIN_Y))+","+STR$(WindowData(0,WIN_W))+","+STR$(WindowData(0,WIN_H)) TEXT 200,4,STR$(WindowClickedOn%) + " " + DebugLine$ + " " + DebugLine2$,,,,FOREGROUND1,BACKGROUND2 ' Draw the Icons FOR I% = 0 TO 99 IF IconData%(I%,ICON_ID) = 0 THEN EXIT FOR IF LCASE$(RIGHT$(IconName$(I%),7))="rubbish" THEN BLIT 320,0,IconData%(I%, ICON_X), IconData%(I%, ICON_Y),32,20,2,&B100 ELSE IF IconData%(I%, ICON_STATUS)=0 THEN ' Closed BLIT 0,0,IconData%(I%, ICON_X), IconData%(I%, ICON_Y),32,20,2,&B100 ELSE ' Open folder BLIT 32,0,IconData%(I%, ICON_X), IconData%(I%, ICON_Y),32,20,2,&B100 END IF END IF ' BOX IconData%(Id% + 1), IconData%(Id% + 2), IconData%(Id% + 3), IconData%(Id% + 4) TEXT IconData%(I%,ICON_X), IconData%(I%, ICON_Y) + IconData%(I%, ICON_H) +4, IconName$(I),,,,FOREGROUND1,BACKGROUND1 NEXT I 'Draw the windows FOR I% = 99 TO 0 STEP -1 IF WindowData%(i%,WIN_Id) <> 0 THEN C3_DrawWindow I% ENDIF NEXT I% ' Draw the System menu if it's open IF SystemMenuOpenState% = 1 THEN RBOX 5,15,150,100,3,RGB(BLACK),BACKGROUND2 END IF C7_DrawMenu IF TextInputWindowType% <> 0 THEN C6_DrawTextInputWindow END IF IF GUIMessage$ <> "" THEN w% = LEN(GUIMessage$) * 6 ' Width in pixels x% = (MM.HRES/2) - (w%/2) y% = (MM.VRES/2) - 20 BOX x%,y%,w% + 10,17,1,RGB(BLACK),RGB(BLACK) ' Draw the drop shadow BOX x%-5,y%-5,w% + 10,17,1,RGB(BLACK),RGB(RED) TEXT x%,y%,GUIMessage,,,,FOREGROUND1,RGB(RED) ENDIF PAGE WRITE 0 PAGE COPY 1 TO 0,B END SUB '******************************************************************************************** ' WINDOW GRAPHICS C3_xxxx '******************************************************************************************** SUB C3_DrawWindow i% LOCAL INTEGER wx%,wy%,ww%,wh% LOCAL INTEGER contentCursor1%, contentCursor2% LOCAL STRING nextfilename$="" LOCAL INTEGER fileType%=0 ' 0=Folder 1=File LOCAL INTEGER iconX% = 4 LOCAL INTEGER iconY% LOCAL INTEGER iconCount1%, iconCount2%, iconSelected% LOCAL INTEGER rowCount1%,rowCount2% LOCAL INTEGER drawIcon%=0, distanceToBottomOfWindow%, blitHeight%, fileTypeIconOffset% LOCAL FLOAT percentage! 'WindowData(600) ' Data is saved as id,x,y,w,h,type where type is 1 = DriveFolder wx=WindowData(i%,WIN_X):wy=WindowData(i%,WIN_Y):ww=WindowData(i%,WIN_W):wh=WindowData(i%,WIN_H) IF i%=0 THEN ' This is the topmost window so draw a drop shadow BOX wx% + 5,wy% + 5,ww%,wh%,1,RGB(Black),RGB(BLACK) END IF BOX wx%,wy%,ww%,wh%,1,RGB(Black),BACKGROUND3 BOX wx%,wy%,ww%,13,1,RGB(Black),BACKGROUND2 LINE wx%+1,wy%+15,wx%+ww%-2,wy%+15,1,RGB(WHITE) LINE wx%,wy%,wx%+ww%-1,wy%,1,RGB(WHITE) LINE wx%,wy%,wx%,wy%+wh%-1,1,RGB(WHITE) C8_DrawButton wx% + ww% - 10,wy% + 1,10,"X" C8_DrawButton wx%,wy% + 1,30,"BACK" TEXT wx% + 40,wy% + 3,WindowPaths$(i%),,,,FOREGROUND1,BACKGROUND2 ' Draw the folders and files nextfilename$="" contentCursor1%=0 contentCursor2%=1 iconCount1%=0 rowCount1%=1 WindowData%(i%,WIN_FIRSTICON)=-1 ' Do a precount of the icons so that we can scale the scrollbar ' This is quite a slow loop - so remember the value in WindowData if we can IF WindowData%(i%,WIN_ICONCOUNT=-1) THEN DO nextFilename$ = C5_GetNextPathWindowContents(i%, contentCursor1%,contentCursor2%) ' The content cursors are updated on the retrn IF nextFilename$ <> "*" AND nextFilename$ <> "\" AND nextFilename$ <> "" THEN iconCount1%=iconCount1%+1 END IF LOOP UNTIL nextFilename$="" rowCount1% = (iconCount1% * 60)/(ww - 4) ' The 4 is because the icons start 4 pixels in from the left WindowData%(i%,WIN_ICONCOUNT)=rowCount1% ELSE rowCount1% = WindowData(i%,WIN_ICONCOUNT) END IF ' The scrollbar top is a percentage of the height of the window percentage! = (WindowData%(i%,WIN_SCROLLBARTOP)) / (wh% - 48) ' Draw the icons contentCursor1%=0 contentCursor2%=1 iconX%=4 iconY%=0 DO nextFilename$ = C5_GetNextPathWindowContents(i%, contentCursor1%,contentCursor2%) ' The content cursors are updated on the retrn IF nextFilename$ = "*" THEN fileType% = 1 ' We're on to the files END IF IF nextFilename$ <> "*" AND nextFilename$ <> "\" AND nextFilename$ <> "" THEN ' If we are here,then we have a valid file or folder name iconSelected% = 0 IF i% = 0 THEN ' Selected icons can only be on top window iconSelected% = C4_IsIconSelected(iconCount2%) END IF ' Check that there is room to display it IF RIGHT$(nextFilename$,1) = "\" THEN nextFilename$ = MID$(nextFilename$,1,LEN(nextFilename$) - 1) END IF IF iconX% + 60 > ww THEN iconX% = 4 iconY% = IconY% + 50 rowCount2% = rowCount2% + 1 END IF drawIcon% = 1 IF rowCount2%<rowCount1%*percentage THEN ' These icons are before the start of the scrollbar drawIcon%=0 iconY% = -50 ' Keep the Y cursor at the top of the window END IF distanceToBottomOfWindow% = wh% - iconY% - 18 IF distanceToBottomOfWindow% < 0 THEN drawIcon% = 0 ' We are off the bottom of the window, so don't blit the icons on to the screen END IF IF drawIcon% = 1 THEN IF WindowData%(i%, WIN_FIRSTICON)=-1 THEN WindowData%(i%, WIN_FIRSTICON)=iconCount2% blitHeight% = 20 IF distanceToBottomOfWindow% < 20 THEN blitHeight% = distanceToBottomOfWindow% IF fileType% = 0 THEN IF iconSelected% = 0 THEN BLIT 0,0,wx% + iconX% + 4, wy + iconY% + 17,32,blitHeight%,2,&B100 ELSE BLIT 32,0,wx% + iconX% + 4, wy% + iconY% + 17,32,blitHeight%,2,&B100 END IF END IF IF fileType% = 1 THEN fileTypeIconOffset% = 64 ' Default file type (this number is used to offset the x location we blit from) IF LCASE$(RIGHT$(nextFilename$,4)) = ".bas" THEN fileTypeIconOffset% = 128 IF LCASE$(RIGHT$(nextFilename$,4)) = ".mod" OR LCASE$(RIGHT$(nextFilename$,4)) = ".wav" OR LCASE$(RIGHT$(nextFilename$,4))=".mp3" OR LCASE$(RIGHT$(nextFilename$,5))=".flac" THEN fileTypeIconOffset% = 192 IF LCASE$(RIGHT$(nextFilename$,4)) = ".png" OR LCASE$(RIGHT$(nextFilename$,4)) = ".bmp" OR LCASE$(RIGHT$(nextFilename$,4))=".jpg" OR LCASE$(RIGHT$(nextFilename$,4))=".gif" THEN fileTypeIconOffset% = 256 IF iconSelected% = 0 THEN BLIT fileTypeIconOffset%,0,wx% + iconX% + 4, wy% + iconY% + 17,32,blitHeight%,2,&B100 ELSE BLIT fileTypeIconOffset% + 32,0,wx% + iconX% + 4, wy% + iconY% + 17,32,blitHeight%,2,&B100 END IF END IF IF distanceToBottomOfWindow% > 30 THEN TEXT wx% + iconX%,wy% + iconY% + 17 + 20, MID$(nextFilename$,1,8),,,,FOREGROUND1,BACKGROUND3 END IF IF distanceToBottomOfWindow% > 40 AND LEN(nextFilename$)>8 THEN TEXT wx% + iconX%,wy% + iconY% + 17 + 30, MID$(nextFilename$,9,8),,,,FOREGROUND1,BACKGROUND3 END IF IF distanceToBottomOfWindow%>50 AND LEN(nextFilename$)>16 THEN TEXT wx% + iconX%,wy% + iconY% + 17 + 40, MID$(nextFilename$,17,8),,,,FOREGROUND1,BACKGROUND3 END IF END IF iconX% = iconX% + 60 iconCount2%=iconCount2% + 1 END IF LOOP UNTIL nextFilename$="" ' Do we need to draw a scrollbar IF drawIcon% = 0 OR percentage! <> 0 THEN ' We ran out of room for icons so we need a scrollbar IF WindowData%(i%,WIN_SCROLLBARTOP) > wh% - 35 THEN WindowData%(i%,WIN_SCROLLBARTOP) = wh% - 35 BOX wx% + ww% - 10,wy% + 16,10,wh% - 25,1,RGB(BLACK),RGB(WHITE) BOX wx% + ww% - 10,wy% + 16 + WindowData%(i%,WIN_SCROLLBARTOP),10,10,1,RGB(BLACK),RGB(BLACK) END IF ' Draw re-sizing grab handle TRIANGLE wx% + ww%,wy% + wh%-10, wx% + ww%,wy% + wh%,wx% + ww% - 10,wy% + wh%,RGB(BLACK),BACKGROUND3 TRIANGLE wx% + ww%,wy% + wh% - 10, wx% + ww% - 10,wy% + wh%-10,wx% + ww%-10,wy% + wh%,RGB(BLACK),BACKGROUND3 END SUB SUB C3_SetMessage message$ GUIMessage=message$ C2_RedrawScreen END SUB SUB PopupMessage message$ C3_SetMessage message$ Pause 3000 C3_SetMessage "" END SUB '******************************************************************************************** ' WINDOW LOGIC C4_xxxx '******************************************************************************************** FUNCTION C4_NextAvailableWindowId() AS INTEGER LOCAL INTEGER i% FOR i% = 0 TO 99 IF WindowData%(i%,WIN_Id)=0 THEN C4_NextAvailableWindowId = i% EXIT FOR END IF NEXT i% END FUNCTION 'FUNCTION FindWindowWithIconId (iconId!) AS INTEGER ' STATIC INTEGER LI ' FOR LI = 0 TO 99 ' IF WindowData(LI,WIN_Id)=iconId THEN ' FindWindowWithIconId=LI ' EXIT FOR ' END IF ' NEXT LI 'END FUNCTION SUB C4_ClearSelectedIcons ' For many functions - we want to unselect any selected icons (for example, navigating to a new directory) MATH SET -1, SelectedIconsInTopWindow%() END SUB SUB C4_AddIconToSelectedIcons WindowIconId%, AllowDeselect ' We maintain a list in SelectedIconsInTopWindow of all of the folders and files that the ' user has selected (by clicking on them). ' Changing directory/Window de-selects the icons - so selected icons are only ever on the top window LOCAL INTEGER q%, d% FOR q% = 0 TO 100 IF SelectedIconsInTopWindow(q%) = WindowIconId% AND AllowDeselect = 1 THEN ' This icon was already selected - so deselect it FOR d% = q% TO 99 SelectedIconsInTopWindow%(d%)=SelectedIconsInTopWindow(d%+1) NEXT d% EXIT SUB END IF IF SelectedIconsInTopWindow%(q%) = WindowIconId% OR SelectedIconsInTopWindow%(q%) = -1 THEN SelectedIconsInTopWindow%(q%) = WindowIconId% EXIT SUB END IF NEXT Q% END SUB SUB C4_CloseWindowWithIconId (iconId%) LOCAL INTEGER found%, i%, q% found% = 0 FOR i% = 0 TO 99 IF found% = 1 THEN ' Move all of the windows up one WindowData%(i% - 1,WIN_Id) = WindowData%(i%,WIN_Id) WindowData%(i% - 1,WIN_X) = WindowData%(i%,WIN_X) WindowData%(i% - 1,WIN_Y) = WindowData%(i%,WIN_Y) WindowData%(i% - 1,WIN_W) = WindowData%(i%,WIN_W) WindowData%(i% - 1,WIN_H) = WindowData%(i%,WIN_H) WindowData%(i% - 1,WIN_TYPE) = WindowData%(i%,WIN_TYPE) WindowData%(i% - 1,WIN_FIRSTICON) = WindowData%(i%,WIN_FIRSTICON) WindowData%(i% - 1,WIN_SCROLLBARTOP) = WindowData%(i%,WIN_SCROLLBARTOP) WindowData%(i% - 1,WIN_ICONCOUNT) = WindowData%(i%,WIN_ICONCOUNT) WindowPaths$(i% - 1) = WindowPaths$(i%) FOR q% = 0 TO 9 WindowContents$(i%-1,q%) = WindowContents$(i%,q%) NEXT q% ELSE IF WindowData%(i%,WIN_Id) = iconId% THEN found%=1 END IF NEXT i% ' Update the original icon that opened it FOR i%=0 TO 99 IF IconData%(i%,ICON_ID) = iconId% THEN IconData%(i%,ICON_STATUS) = 0 ' Not open EXIT FOR END IF NEXT i% C4_ClearSelectedIcons ' Unselect and selected icons - i.e. no icons are selected END SUB SUB C4_MoveWindowToTop index% LOCAL INTEGER temporaryData%(9), i%, j% LOCAL STRING temporaryPath, temporaryContents(10) FOR i% = 0 to 8 temporaryData%(i%) = windowData%(index%,i) NEXT i% ' Move everything down one to make room FOR i%=index% TO 1 STEP -1 WindowData%(i%,WIN_Id) = WindowData%(i% - 1,WIN_Id) WindowData%(i%,WIN_X) = WindowData%(i% - 1,WIN_X) WindowData%(i%,WIN_Y) = WindowData%(i% - 1,WIN_Y) WindowData%(i%,WIN_W) = WindowData%(i% - 1,WIN_W) WindowData%(i%,WIN_H) = WindowData%(i% - 1,WIN_H) WindowData%(i%,WIN_TYPE) = WindowData%(i% - 1,WIN_TYPE) WindowData%(i%,WIN_FIRSTICON) = WindowData%(i% - 1,WIN_FIRSTICON) WindowData%(i%,WIN_SCROLLBARTOP) = WindowData%(i% - 1,WIN_SCROLLBARTOP) WindowData%(i%,WIN_ICONCOUNT) = WindowData%(i% - 1,WIN_ICONCOUNT) NEXT i% FOR i% = 0 to 8 WindowData%(0,i%) = temporaryData%(i) NEXT i 'Move the WindowPaths a well temporaryPath$ = WindowPaths$(index%) FOR i% = 0 TO 9 temporaryContents$(i%) = WindowContents$(index%,i) NEXT i% FOR i% = index% to 1 STEP -1 WindowPaths$(i%) = WindowPaths$(i%-1) FOR j% = 0 TO 9 WindowContents$(i%,j%) = WindowContents$(i% - 1, j) NEXT j% NEXT i% WindowPaths$(0) = temporaryPath$ FOR i% = 0 TO 9 WindowContents$(0,i%) = temporaryContents$(i%) NEXT i% END SUB FUNCTION C4_IsIconSelected(IconId%) AS INTEGER LOCAL INTEGER j% FOR j%=0 TO 100 IF SelectedIconsInTopWindow%(j%) = IconId% THEN C4_IsIconSelected = 1 EXIT FUNCTION END IF IF SelectedIconsInTopWindow%(j%) = -1 THEN EXIT FOR END IF NEXT j% C4_IsIconSelected=0 END FUNCTION '******************************************************************************************** ' WINDOW CONTENTS ARRAY C5_xxxx '******************************************************************************************** ' Keeping track of the contents of each Window can get a little complex. Therefore, we use the following arrays ' WindowPaths$(WindowId) - The file path that this window represents ' WindowData(WindowId, Index) - Where Index is as follows (this stores the WIndowx,y, height, width and type) ' CONST WIN_ID=0 ' CONST WIN_X=1 ' CONST WIN_Y=2 ' CONST WIN_W=3 ' CONST WIN_H=4 ' CONST WIN_TYPE=5 ' Always 1 for the time being to represent a window with files/folders in SUB C5_UpdateWindowContents windowId% LOCAL STRING path$, f$ LOCAL INTEGER arraySubIndex%, i% path$ = WindowPaths$(windowId%) CHDIR path$ arraySubIndex% = 0 FOR i% = 0 TO 9 WindowContents$(windowId%,i%) = "" NEXT i% f$=DIR$("*",DIR) DO WHILE f$ <> "" arraysubIndex% = C5_AddToWindowContentsArray(windowId%, arraySubIndex%, "\" + f$) f$=DIR$() LOOP arraySubIndex% = C5_AddToWindowContentsArray(windowId%, arraySubIndex%, "\*") ' Split to show that the rest are files f$=DIR$("*",FILE) DO WHILE f$ <> "" arraySubIndex% = C5_AddToWindowContentsArray(windowId%, arraySubIndex%, "\" + f$) f$=DIR$() LOOP WindowData(windowId%, WIN_ICONCOUNT) = -1 ' Forces the icons to be recounted next time the window is drawn END SUB FUNCTION C5_GetItemWinContentsArray(winId%, index%) AS STRING ' Without data structures, we are storing the sub-directory paths and file paths as strings ' in arrays WindowContents(100,10), The first parametr is the Window Id, and because we are limited to ' 255 charcters per string, we use up to ten strings (the second index). ' Paths are stored with Sub-Directories first, then an * (to seperate sub directories from files) and the ' the filenames. All paths are divided by a '\' charcter. LOCAL STRING nextfilename$="" LOCAL INTEGER contentCursor1%=0, contentCursor2%=1, iconCount%, filetype% DO nextFilename$ = C5_GetNextPathWindowContents(winId%, contentCursor1%,contentCursor2%) ' The content cursors are updated on the return IF nextFilename$ = "*" THEN fileType% = 1 ' We're on to the files END IF IF nextFilename$ <> "*" AND nextFilename$ <> "\" THEN IF iconCount% = index% THEN ' This is the icon we are looking for IF RIGHT$(nextFilename$,1) = "\" THEN nextFilename$=MID$(nextFilename$,1,LEN(nextFilename$)-1) END IF IF fileType% = 0 THEN C5_GetItemWinContentsArray = "D" + nextFilename$ ' Add the D for directory ELSE C5_GetItemWinContentsArray = "F" + nextFilename$ ' Add the F for File END IF EXIT FUNCTION ENDIF iconCount% = iconCount% + 1 ENDIF LOOP UNTIL nextFilename$="" END FUNCTION FUNCTION C5_AddToWindowContentsArray(winId%, index%, value$) AS INTEGER LOCAL newIndex% = index% IF LEN(WindowContents(winId%,newIndex%)) + LEN(value$) > 254 THEN ' The list of folders/files is too long we need to add it to the next line in the array newIndex% = newIndex% + 1 IF newIndex% = 10 THEN C5_AddToWindowContentsArray = 9: EXIT FUNCTION ' We've run out of space in the storage array, so stop adding to it END IF WindowContents$(winId%,newIndex%) = WindowContents$(winId%,newIndex%) + value$ C5_AddToWindowContentsArray = newIndex% END FUNCTION FUNCTION C5_GetNextPathWindowContents(windowId%, lastCursor1%, lastCursor2%) AS STRING LOCAL STRING a$, b$ LOCAL INTEGER i% C5_GetNextPathWindowContents="" ' Check if this is the end of this string and we need to move to the next in the array IF LEN(WindowContents$(windowId%, lastCursor1%)) = lastCursor2% - 1 THEN lastCursor1% = lastCursor1% + 1 lastCursor2% = 1 IF lastCursor1% = 10 THEN EXIT FUNCTION ' End of contents array END IF END IF a$ = WindowContents$(windowId%,lastCursor1%) b$ = MID$(A$, lastCursor2%) IF MID$(b$,1,1) = "*" THEN ' This is the marker to change from folders to files lastCursor2% = lastcursor2% + 1 C5_GetNextPathWindowContents = "*" ELSE ' Look Through b$ for the next divider (* between folders and files and \ between each record) FOR i%=1 TO LEN(b$) IF MID$(b$,i%,1)="\" OR i%=LEN(b$) THEN ' This is the next file or folder C5_GetNextPathWindowContents=MID$(b$,1,i%) lastCursor2%=lastCursor2% + i% ' The position of the next filename - remember passed by ref EXIT FOR END IF NEXT i% ENDIF END FUNCTION '******************************************************************************************** ' TEXT INPUT WINDOW C6_xxxx '******************************************************************************************** ' When we have to ask the user for text input (such as a rename or new directory name) we use the ' Text Input Window SUB C6_DrawTextInputWindow LOCAL INTEGER w%=400 'Width in pixels LOCAL INTEGER x%=(MM.HRES/2)-(w%/2) LOCAL INTEGER y%=(MM.VRES/2)-20 BOX x%,y%,w%+10,50,1,RGB(BLACK),RGB(BLACK) ' Draw the drop shadow BOX x% - 5,y% - 5,w% + 10,50,1,RGB(BLACK),BACKGROUND3 IF TextInputWindowType% = 1 THEN TEXT x%,y%,"Rename directory or file",,,,FOREGROUND1,BACKGROUND3 IF TextInputWindowType% = 2 THEN TEXT x%,y%,"New directory",,,,FOREGROUND1,BACKGROUND3 'Draw the inputted text BOX x% + 10,y% + 15,w% - 20,12,1,RGB(BLACK),RGB(WHITE) TEXT x% + 12,y% + 17,TextInputWindowContents$,,,,RGB(BLACK),RGB(WHITE) C8_DrawButton x% + 10,y% + 30,40,"CANCEL" C8_DrawButton x% + w% - 60,y% + 30,40,"OKAY" END SUB SUB C6_ProcessTextInputWindow LOCAL INTEGER w% = 400 'Width in pixels LOCAL INTEGER x% = (MM.HRES/2) - (w%/2) LOCAL INTEGER y% = (MM.VRES/2) - 20 IF (MouseY% > y% + 30 AND MouseY% < y% + 42 AND MouseX% > x% + 10 AND MouseX% < x% + 50) OR EscapePressed% = 1 THEN ' CANCEL Clicked TextInputWindowType% = 0 ENDIF IF (MouseY% > y% + 30 AND MouseY% < y% + 42 AND MouseX% > x% + w% - 60 AND MouseX% < x% + w% - 20) OR EnterPressed% = 1 THEN ' OKAY Clicked ' Check if the rename or new directory function is going to clash with an existing file or directory name LOCAL STRING newFullPath$ = WindowPaths$(0) + "\" + TextInputWindowContents$ IF TextInputWindowType% = 1 OR TextInputWindowType% = 2 THEN IF MM.INFO(EXISTS FILE newFullPath$) OR MM.INFO(EXISTS DIR newFullPath$) THEN PopupMessage newFullPath$ + " already exists" EXIT SUB ENDIF ENDIF IF TextInputWindowType% = 1 THEN ' Rename RENAME TextInputWindowOriginal$ AS newFullPath$ C5_UpdateWindowContents 0 ' Make sure to update so we can see the new directory ENDIF IF TextInputWindowType=2 THEN ' New Directory MKDIR newFullPath$ C5_UpdateWindowContents 0 ' Make sure to update so we can see the new directory ENDIF TextInputWindowType% = 0 ENDIF EnterPressed%=0 EscapePressed%=0 C2_RedrawScreen END SUB SUB C6_OpenTextInputWindowAsRename ' Make sure only one icon is selected IF SelectedIconsInTopWindow%(0) = -1 OR SelectedIconsInTopWindow%(1) <> -1 THEN EXIT SUB LOCAL STRING s$ = C5_GetItemWinContentsArray(0,SelectedIconsInTopWindow%(0)) TextInputWindowContents$ = MID$(s$,2) TextInputWindowOriginal$ = WindowPaths$(0) + "\" + TextInputWindowContents$ TextInputWindowType%=1 ' Rename directory END SUB SUB C6_OpenTextInputWindowAsNewDir TextInputWindowContents$="New_Directory" TextInputWindowOriginal$="" TextInputWindowType%=2 ' Make new directory END SUB '******************************************************************************************** ' MENU GRAPHICS AND LOGIC C7_xxxx '******************************************************************************************** SUB C7_SetMenu id%,button1$,button2$,button3$,button4$,button5$,button6$,button7$,button8$ LOCAL INTEGER itemCount%, menuWidth%, i% GUIMenuOptions$(0)=button1$:GUIMenuOptions$(1)=button2$:GUIMenuOptions$(2)=button3$ GUIMenuOptions$(3)=button4$:GUIMenuOptions$(4)=button5$:GUIMenuOptions$(5)=button6$ GUIMenuOptions$(6)=button7$:GUIMenuOptions$(7)=button8$ FOR i% = 0 TO 7 IF GUIMenuOptions$(i%) <> "" THEN itemCount%=itemCount%+1 IF LEN(GUIMenuOptions$(i%)) * 6 > menuWidth% THEN menuWidth% = LEN(GUIMenuOptions$(i%))*6 NEXT i% GUIMenuParameters%(MENU_ID) = id% GUIMenuParameters%(MENU_X) = MouseX GUIMenuParameters%(MENU_Y) = MouseY GUIMenuParameters%(MENU_H) = itemCount% * 10 GUIMenuParameters%(MENU_W) = menuWidth% END SUB SUB C7_DrawMenu LOCAL INTEGER i%,left%,top%,width%,height% IF GUIMenuParameters%(MENU_ID) = 0 THEN EXIT SUB ' There is no menu left% = GUIMenuParameters%(MENU_X) top% = GUIMenuParameters%(MENU_Y) width% = GUIMenuParameters%(MENU_W) height% = GUIMenuParameters%(MENU_H) BOX left% - 3,top% - 6,60,10,1,RGB(BLACK),RGB(BLACK) ' Drop Shadow 1 BOX left% + 2,top% + 2,width% + 6,height% + 6,1,RGB(BLACK),RGB(BLACK) ' Drop Shadow 2 BOX left% - 3,top% - 3,width% + 6,height% + 6,1,RGB(BLACK),MENUBACKGROUND1 ' Menu box ' Draw menu title BOX left% - 8,top% - 12,60,10,1,RGB(BLACK),BACKGROUND2 TEXT left% - 6,top% - 11,"File Menu",,,,RGB(WHITE),BACKGROUND2 FOR i% = 0 TO 7 IF GUIMenuOptions$(i%)<>"" THEN IF GUIMenuCurrentItem% <> i% THEN TEXT left%, top% + i% * 10, GUIMenuOptions$(i%),,,,MENUFOREGROUND1,MENUBACKGROUND1 ELSE BOX left% - 2,top% + i * 10,width% + 4,10,1,MENUBACKGROUND2,MENUBACKGROUND2 TEXT left%,top% + i% * 10,GUIMenuOptions$(i%),,,,MENUFOREGROUND1,MENUBACKGROUND2 LINE left% - 2,top% + i * 10 - 1,left%+width%,top% + i * 10 - 1,1,RGB(BLACK) LINE left% - 2,top% + i * 10 + 8,left% + width%,top% + i * 10 + 8,1,RGB(WHITE) END IF 'Look for special cases where menu items should be disabled IF GUIMenuParameters%(MENU_ID) = 1 AND (i% = 1 OR i% = 2) THEN ' Paste or Move option IF PathToCopyFrom$ = WindowPaths$(0) OR FilenamesToCopy$(0) = "" THEN 'Source=Dest or No files selected TEXT left%,top% + i% * 10,GUIMenuOptions$(i%),,,,RGB(LIGHTGREY),MENUBACKGROUND1 END IF ENDIF IF GUIMenuParameters%(MENU_ID) = 1 AND i% = 4 THEN ' Link to desktop option IF SelectedIconsInTopWindow%(0) = -1 OR SelectedIconsInTopWindow%(1)<>-1 THEN ' Is only enabled if only one file selected TEXT left%,top% + i% * 10,GUIMenuOptions$(i%),,,,RGB(LIGHTGREY),MENUBACKGROUND1 END IF ENDIF IF GUIMenuParameters%(MENU_ID) = 1 AND i% = 5 THEN ' Delete option IF SelectedIconsInTopWindow%(0) = -1 THEN ' No files selected TEXT left%,top% + i% * 10,GUIMenuOptions$(i%),,,,RGB(LIGHTGREY),MENUBACKGROUND1 END IF ENDIF END IF NEXT i% END SUB SUB C7_CloseMenu IF GUIMenuParameters%(MENU_ID) <> 0 THEN ' A menu is open - so need to refresh the screen to show it closed GUIMenuParameters%(MENU_ID) = 0 C2_RedrawScreen ENDIF END SUB SUB C7_SystemMenuOpenClose ' This refers to the menu at the top left of the screen ' We're not doing anything with it yet IF SystemMenuOpenState% = 0 THEN SystemMenuOpenState% = 1 'Open ELSE SystemMenuOpenState% = 0 ' Closed END IF C2_RedrawScreen END SUB SUB C7_RefreshGUIMenu ' Called every pass and during click event STATIC INTEGER last% ' Important that this is STATIC C7_UpdateGUIMenuCurrentItem IF GUIMenuCurrentItem% <> last% THEN last% = GUIMenuCurrentItem% C2_RedrawScreen END IF END SUB SUB C7_UpdateGUIMenuCurrentItem ' Called from C7_RefreshGUIMenu GUIMenuCurrentItem% = -1 ' Assume no menu option is being hovered over IF GUIMenuParameters%(MENU_ID) = 0 THEN ' There is no menu EXIT SUB END IF IF MouseX% < GUIMenuParameters%(MENU_X) OR MouseX% > GUIMenuParameters%(MENU_X) + GUIMenuParameters%(MENU_W) THEN EXIT SUB IF MouseY% < GUIMenuParameters%(MENU_Y) OR MouseY% > GUIMenuParameters%(MENU_Y) + GUIMenuParameters%(MENU_H) THEN EXIT SUB ' If we are here, then the mouse is hovering somewhere within the menu GUIMenuCurrentItem% = (MouseY% - GUIMenuParameters%(MENU_Y) - 5) / 10 END SUB '******************************************************************************************** ' GENERIC GRAPHICS C8_xxxx '******************************************************************************************** SUB C8_DrawButton x%,y%,w%,contents$ BOX x%,y%,w%,12,1,RGB(BLACK),RGB(GREY) LINE x%,y%,x%,y% + 11,1,RGB(WHITE) LINE x%,y%,x% + w% - 1,y%,1,RGB(WHITE) TEXT x% + (w% / 2) - (LEN(contents$) * 3),y% + 2,contents$,,,,RGB(WHITE),RGB(GREY) ' Center text on button END SUB '******************************************************************************************** ' FILE HANDLING C9_xxxx '******************************************************************************************** SUB C9_CopyButtonPressed LOCAL INTEGER i% LOCAL STRING filename$ ' Clear out any old data FOR i%=0 TO 100:FilenamesToCopy(i%)="":Next i% PathToCopyFrom$ = WindowPaths$(0) FOR i% = 0 TO 100 IF SelectedIconsInTopWindow%(i%) = -1 THEN EXIT FOR filename$ = C5_GetItemWinContentsArray(0,SelectedIconsInTopWindow%(i%)) FilenamesToCopy$(i%) = MID$(filename$,2) NEXT I% END SUB SUB C9_PasteButtonPressed LOCAL INTEGER i% LOCAL STRING fromFileName$, toFilename$ IF WindowPaths$(0) = "" THEN EXIT SUB ' Need a window to paste into IF WindowPaths$(0) = PathToCopyFrom$ THEN EXIT SUB ' Trying to paste into source folder FOR i% = 0 TO 100 IF FilenamesToCopy$(i%)= "" THEN EXIT FOR ' End of list fromFilename$ = PathToCopyFrom$ + "\" + FilenamesToCopy$(i%) IF MM.INFO$(EXISTS FILE fromFilename$) THEN ' It's a file toFilename$ = WindowPaths$(0) + "\" + FilenamesToCopy$(i%) COPY fromFilename$ TO toFilename$ END IF IF MM.INFO$(EXISTS DIR fromFilename$) THEN 'It's a folder. Recursively copy all of the folder contents C9_RecursiveFolderCopy PathToCopyFrom$, WindowPaths$(0), FilenamesToCopy$(i%) END IF NEXT i% C5_UpdateWindowContents 0 ' Refresh the contents of the top screen to include the new files C4_ClearSelectedIcons ' Unselect and selected icons - i.e. no icons are selected C2_RedrawScreen END SUB SUB C9_RecursiveFolderCopy fromParent$, toParent$, folderName$ ' For example to copy A:\Parent\Folder to A:\NewParent\ ' we would use "A:\Parent", "A:\NewParent", "Folder" ' If you're struggling to understand the concept of this call, then the keywords you ' want to lookup are "Recursive calls" or "Tree walking". But basically this routine gets called ' for every folder in the tree structure below the folder being copied. Importantly, because ' the variables are defined as "LOCAL" every time the subroutine is called it gets it's own set ' of variables. Therefore, then can be three or four (or more) copies of this subroutine being ' used a any one time. As we finish processing each folder, w might dive into a sub folder, or if ' there are no more subfolders to process, then we end that copy of this subroutine and step back ' up the directory structure. Eventually we will have worked through all of the sub folders and ' returned to th orginal top level folder. ' Hint: Each "copy" of this subroutine in memory is called an "instance" LOCAL STRING f$, fromFolder$, toFolder$ LOCAL STRING listOfFolders$(100) LOCAL INTEGER CountOfFolders%, i% fromFolder$ = fromParent$ + "\" + folderName$ toFolder$ = toParent$ + "\" + folderName$ ' Make sure the folder exists at the destintion. If not, then create it IF MM.INFO$(EXISTS DIR toFolder$) = 0 THEN MKDIR toFolder$ END IF CHDIR fromFolder$ ' Get all of the files within this folder and copy them f$=DIR$("*",FILE) DO WHILE f$ <> "" C3_SetMessage "Copying " + toFolder$ + "\" + f$ COPY fromFolder$ + "\" + f$ TO toFolder$ + "\" + f$ f$=DIR$() LOOP ' Get all of the folders in this folder ' Note: We have to put them in a local array of strings so that it stays with this "copy" ' or "instance" of the Subroutine. CountOfFolders% = 0 f$=DIR$("*",DIR) DO WHILE f$ <> "" ListOfFolders$(CountOfFolders%)=f$ CountOfFolders%=CountOfFolders%+1 f$=DIR$() LOOP ' This is the recursive bit. For each folder, call this subroutine again and start copying it's ' contents to the matching folder in the destination file structure FOR i% = 0 TO CountOfFolders - 1 C9_RecursiveFolderCopy fromFolder$, toFolder$, ListOfFolders$(i%) NEXT i% C3_SetMessage "" END SUB SUB C9_DeleteSelectedFiles LOCAL STRING rubbishPath$ IF MM.INFO(EXISTS DIR "A:\GUI")=0 THEN MKDIR "A:\GUI" ENDIF IF MM.INFO(EXISTS DIR "A:\GUI\RUBBISH")=0 THEN MKDIR "A:\GUI\RUBBISH" ENDIF ' Make a specific folder for this deletion rubbishPath$="A:\GUI\RUBBISH\" + DATE$ + " " + MID$(TIME$,1,2) + MID$(TIME$,4,2) + MID$(TIME$,7,2) MKDIR rubbishPath$ C9_CopyButtonPressed ' FilenamesToCopy() now has the list of files/folders to delete C9_MoveSelectedFiles rubbishPath$ END SUB SUB C9_MoveSelectedFiles destination$ LOCAL INTEGER i% LOCAL STRING fileToMove$ IF PathToCopyFrom$ = destination$ THEN EXIT SUB ' The source is the same as the destination FOR i% = 0 TO 100 IF FilenamesTocopy(i%)= "" THEN EXIT FOR ' End of list fileToMove$ = PathToCopyFrom + "\" + FilenamesToCopy(i%) IF MM.INFO$(EXISTS FILE fileToMove$) THEN ' It's a top level file. Move it to the destination RENAME fileToMove$ AS destination$ + "\" + FilenamesToCopy(i%) END IF IF MM.INFO$(EXISTS DIR fileToMove$) THEN 'It's a folder. Move it to the destination IF MID$(destination$,1,LEN(fileToMove$)) = fileToMove$ THEN EXIT SUB 'destination is sub folder of source RENAME fileToMove$ AS destination$ + "\" + FilenamesToCopy$(i%) END IF NEXT I% C5_UpdateWindowContents 0 ' Refresh the contents of the top screen to inlcude the new files C4_ClearSelectedIcons ' Unselect and selected icons - i.e. no icons are selected C2_RedrawScreen END SUB FUNCTION C9_GetParentPath (path$) AS STRING STATIC INTEGER length%,cursor% C9_GetParentPath = path$ length% = LEN(path$) FOR cursor% = length% TO 1 STEP -1 IF MID$(path$,cursor%,1)="\" THEN C9_GetParentPath = MID$(path$,1,cursor% - 1) EXIT FOR END IF NEXT cursor% END FUNCTION SUB C9_ReadInsertedSDCard LOCAL INTEGER i% IF MM.INFO$(SDCARD) = SDCardStatus$ THEN EXIT SUB ' Nothing has changed SDCardStatus$ = MM.INFO$(SDCARD) ' Clear out any old data FOR i% = 0 TO 100 IconData%(i%,ICON_ID) = 0:IconData%(i%,ICON_X) = 0:IconData%(i%,ICON_Y) = 0:IconData%(i%,ICON_W) = 0 IconData%(i%,ICON_H) = 0:IconData%(i%,ICON_STATUS)=0:IconName$(i%)="" NEXT i% IF SDCardStatus$<>"Ready" AND SDCardStatus$<>"Unused" THEN SDCardOK% = 0 C2_RedrawScreen EXIT SUB END IF IF SDCardStatus$ = "Unused" THEN ' We need this logic to flip that status bit back to "Ready" PAUSE(100) PRINT MM.INFO(EXISTS DIR "A:") END IF ' If we are here, then the SD card is okay SDCardOK% = 1 IF MM.INFO(EXISTS DIR "A:\GUI") = 0 THEN MKDIR "A:\GUI" ENDIF IF MM.INFO(EXISTS FILE "A:\GUI\guidrives.dat") = 0 THEN ' There is no config file, so write out the default file OPEN "A:\GUI\guidrives.dat" FOR OUTPUT AS #1 PRINT #1,"A:" ' Name, id,x,y,w,h,status PRINT #1,1:PRINT #1,10:PRINT #1,20:PRINT #1,32:PRINT #1,20:PRINT #1,0 PRINT #1,"A:\GUI\Rubbish" PRINT #1,2:PRINT #1,10:PRINT #1,80:PRINT #1,32:PRINT #1,20:PRINT #1,0 CLOSE #1 END IF i%=0 OPEN "A:\GUI\guidrives.dat" FOR INPUT AS #1 DO WHILE NOT EOF(#1) LINE INPUT #1,IconName$(i%) INPUT #1,IconData%(i%,ICON_ID) INPUT #1,IconData%(i%,ICON_X) INPUT #1,IconData%(i%,ICON_Y) INPUT #1,IconData%(i%,ICON_W) INPUT #1,IconData%(i%,ICON_H) INPUT #1,IconData%(i%,ICON_STATUS) IconData%(I%, ICON_STATUS) = 0 ' For the time being when w start the program all windows are closed ' PRINT IconData(i%,ICON_X) i%=i%+1 LOOP CLOSE #1 IF IconData%(i%,ICON_ID) = 0 THEN ' If we are here, then something has gone wrong with the file (corrupted or locked) ' Revert to default values IconData%(0,ICON_ID) = 1:IconData%(0,ICON_X) = 10:IconData%(0,ICON_Y) = 30:IconData%(0,ICON_W) = 32 IconData%(0,ICON_H) = 20:IconData%(0,ICON_STATUS) = 0:IconName$(0) = "A:" END IF C2_RedrawScreen END SUB SUB C9_SaveDesktopPaths LOCAL INTEGER i% OPEN "A:\GUI\guidrives.dat" FOR OUTPUT AS #1 FOR i%=0 TO 99 IF IconName$(i%)<>"" THEN PRINT #1,IconName$(i%) PRINT #1,IconData%(i%,ICON_ID) PRINT #1,IconData%(i%,ICON_X) PRINT #1,IconData%(i%,ICON_Y) PRINT #1,IconData%(i%,ICON_W) PRINT #1,IconData%(i%,ICON_H) PRINT #1,IconData%(i%,ICON_STATUS) END IF NEXT i% CLOSE #1 END SUB SUB C9_AddSelectedIconsToDesktop ' Currently only allowed to select single item and add it - the code will handle mutliple ' items being added, but the desktop may get messy IF SelectedIconsInTopWindow%(0) = -1 OR SelectedIconsInTopWindow%(1) <> -1 THEN EXIT SUB LOCAL INTEGER nextId% = 0 ' Integers are 64 bit - so no issue with running out LOCAL INTEGER lastCursor% = 0 ' Location of last entry in IconData LOCAL INTEGER i%, column%, row% FOR i%=0 TO 99 IF IconData%(i%,ICON_ID) >= nextId% THEN nextId% = IconData%(i%,ICON_ID) + 1 IF IconData%(i%,ICON_ID) <> 0 THEN lastCursor% = i% NEXT i% FOR i% = 0 TO 99 IF SelectedIconsInTopWindow%(i%) <> -1 THEN lastCursor% = lastCursor% + 1 ' Incrememnt to next location IF lastCursor% = 100 THEN EXIT SUB ' Maximum number of desktop icons is 100 IconName$(lastCursor%) = WindowPaths$(0) + "\" + MID$(C5_GetItemWinContentsArray(0,SelectedIconsInTopWindow(i%)),2) IconData(lastCursor,ICON_ID) = nextId% column% = ((lastCursor% * 60) + 20) / (MM.VRES - 20) row% = lastCursor% - (column% * ((MM.VRES - 20) / 60)) IconData%(lastCursor%,ICON_X) = 10 + column% * 60 IconData%(lastCursor%,ICON_Y) = 20 + row%*60 IconData%(lastCursor%,ICON_W) = 32 IconData%(lastCursor%,ICON_H) = 20 IconData%(lastCursor%,ICON_STATUS) = 0 nextId% = nextId% + 1 END IF NEXT i% C9_SaveDesktopPaths C2_RedrawScreen END SUB SUB C9_RemoveSelectedIconFromDesktop FOR I% = SelectedDesktopIcon% TO 98 ' Move the icon details up the stack to erase the old icon - however - leave the icon position intact ' So that we fill in the gap on the screen IconName$(I%) = IconName$(I% + 1) IconData(I%, ICON_ID) = IconData%(I% + 1, ICON_ID) IconData%(I%, ICON_STATUS) = IconData%(I% + 1, ICON_STATUS) NEXT I% IconName$(99) = "" IconData%(99, ICON_ID) = 0 IconData%(99, ICON_STATUS) = 0 C9_SaveDesktopPaths C2_RedrawScreen END SUB SUB C9_CopyGUIFilesToDisk IF MM.INFO(EXISTS DIR "A:\GUI") = 0 THEN MKDIR "A:\GUI" ENDIF IF MM.INFO(EXISTS DIR "A:\GUI\RUBBISH")=0 THEN MKDIR "A:\GUI\RUBBISH" ENDIF END SUB |
||||
PeteCotton Guru Joined: 13/08/2020 Location: CanadaPosts: 367 |
And icons are stored in a .png file under \img\Icons2.png |
||||
mclout999 Guru Joined: 05/07/2020 Location: United StatesPosts: 469 |
Well, it could be extended to be GEM for CMM2 if you like. Add some apps and control panel to customize the look etc. |
||||
PhenixRising Guru Joined: 07/11/2023 Location: United KingdomPosts: 852 |
Oh, very cool, Pete This is the sort of thing I've been wanting to see and I love the code format/structure |
||||
PhenixRising Guru Joined: 07/11/2023 Location: United KingdomPosts: 852 |
|
||||
Volhout Guru Joined: 05/03/2018 Location: NetherlandsPosts: 4212 |
Pete, Nice work. But I fail to see the relation to the Magnetic Scrolls adventures. And I would LOVE to play those again. They where amongst my most favourite. I still have somewhere a printed award for "Master Thief" I received for finishing the game (together with a friend, we played 1 evening every 2 weeks in a time where no walkthrough's existed). So you had to find out everything yourself. No Help.... For one of the adventures I even wrote a maze solver, after (try and error) I understood the maze generation algorithm in the game. The Pawn, Fish, Guild of Thieves, played them all.... Volhout Edited 2024-11-19 17:07 by Volhout PicomiteVGA PETSCII ROBOTS |
||||
thwill Guru Joined: 16/09/2019 Location: United KingdomPosts: 4026 |
That's a very nice graphical shell Pete. Tom Game*Mite, CMM2 Welcome Tape, Creaky old text adventures |
||||
lizby Guru Joined: 17/05/2016 Location: United StatesPosts: 3150 |
What a terrific piece of code, Pete. Congratulations. PicoMite, Armmite F4, SensorKits, MMBasic Hardware, Games, etc. on fruitoftheshed |
||||
PeteCotton Guru Joined: 13/08/2020 Location: CanadaPosts: 367 |
Thank you all for the kind words. It was a lot fun to play with. |
||||
PhenixRising Guru Joined: 07/11/2023 Location: United KingdomPosts: 852 |
|
||||
PeteCotton Guru Joined: 13/08/2020 Location: CanadaPosts: 367 |
Yes - I did think that it would be useful as a "Programmers toolkit" with RGB sliders to show the various colours, a Binary/Hex calculator, sprite editor and maybe even a text editor (with tabs for different files). I imagined the add-ons could be written in #INCLUDE files, and call a common API for windows, buttons etc. But I'm not pushed about turning it into a productivity suite (Word processing and spreadsheets) as there are already incredibly capable systems out there (although if anyone wants to do it as an exercise - be my guest). All around it was just a bit of fun - but I am very happy with the way it turned out. |
||||
PeteCotton Guru Joined: 13/08/2020 Location: CanadaPosts: 367 |
I played on the Amiga - so it might have been different on other versions. But the graphics were on a sliding window that could be dragged up and down over the text (it seems so trivial now - but I remember being wowed at the time). At the bottom of this overlay window were four buttons which opened drop down menus. That was the genesis of this whole project. I was thinking it would be nice to add a lot more as well, such as an auto-drawing map and roving characters who would be overlayed on the background images. Edited 2024-11-20 02:18 by PeteCotton |
||||
mclout999 Guru Joined: 05/07/2020 Location: United StatesPosts: 469 |
For sh*ts and giggles, it might be fun to see someone start a different project called CMM2 BOB. I mean, Microsoft thought that was the next logical step in GUI. development. I like the GEM CMM2 myself but Then I guess I'm just anachronistic and too inflexible to see the wonders of BOB! (I still have a boxed copy around here, isn't that sad) |
||||
Mixtel90 Guru Joined: 05/10/2019 Location: United KingdomPosts: 6760 |
Not PicoBOB? :) Mick Zilog Inside! nascom.info for Nascom & Gemini Preliminary MMBasic docs & my PCB designs |
||||
PeteCotton Guru Joined: 13/08/2020 Location: CanadaPosts: 367 |
Wow! I'd forgotten all about BOB (had to google). https://en.wikipedia.org/wiki/Microsoft_Bob That'd be a cool project for someone to do. Part of my thinking for the GUI was to make the machine more accessible for first time users (running games etc.). It's the basics of building a drug empire. Get them hooked on the product (games), then introduce to them to the really fun stuff (programming). |
||||
Mixtel90 Guru Joined: 05/10/2019 Location: United KingdomPosts: 6760 |
You know, something like the BOB concept could even work on a Pico. If background screens, icons etc. were held in a flash slot and pulled out when needed it wouldn't hit the RAM too hard as the big stuff would be simple graphics loading, I suppose that could be direct to the framebuffer? The mouse would be on a layer. The BOB routines could be in the Library to make it easy to call from anywhere. Mick Zilog Inside! nascom.info for Nascom & Gemini Preliminary MMBasic docs & my PCB designs |
||||
PhenixRising Guru Joined: 07/11/2023 Location: United KingdomPosts: 852 |
Hey Pete, how did you capture that video of the CMM2? |
||||
PeteCotton Guru Joined: 13/08/2020 Location: CanadaPosts: 367 |
With these two doo-hickeys. A VGA to HDMI connector to convert the VGA out to HDMI. https://www.amazon.ca/gp/product/B083K83NNX/ref=ppx_yo_dt_b_search_asin_title?ie=UTF8&psc=1 An HDMI capture card. I just used Windows Camera app to record the incoming video (if you open Windows Camera app - it sees the capture card as a web cam). https://www.amazon.ca/gp/product/B0CLNZ8267/ref=ppx_yo_dt_b_search_asin_title?ie=UTF8&th=1 The whole setup works well, because I can also pass through the HDMI to my HDMI splitter box (not required to record the video). This gives me the options of how to use the output: video out from the CMM2 to a Window on my PC (via Camera app) or straight through to my secondary monitor - or both. Or use the secondary monitor for my PC (usually), my Mac (boo-hiss - I have to have one to compile iPhone projects), or whatever Raspberry Pi I am mucking about with. https://www.amazon.ca/gp/product/B071G4NXNH/ref=ppx_yo_dt_b_search_asin_title?ie=UTF8&psc=1 Please forgive the incredibly messy desk - but as you can see, the CMM2 is feeding through to the 24" secondary screen, and if I'm feeling particularly blind it's also displaying in a window on my 55" PC monitor. Interestingly, the Webcam input (from the capture card) reacts to mode changes much faster than the 24" monitor (it is an old monitor > 10 years). If I change mode, my monitor blanks for a few seconds before locking onto the new display mode. Modern monitors are probably much faster at picking up the new signal. Edited 2024-11-21 06:17 by PeteCotton |
||||
PhenixRising Guru Joined: 07/11/2023 Location: United KingdomPosts: 852 |
If yer desk was tidy... I wouldn't trust/respect ya |
||||
Page 1 of 2 |
Print this page |