QB CULT MAGAZINE
Vol. 3 Iss. 2 - November 2003

Making a formatted text viewer using DQB - Part 1

By Zap <RuneMoennike@hotmail.com>

So... You want to know how to do a textviewer in Qb...
Hu? You don't? Hmm... What if I tell you that I'll learn how to do a formatted text viewer in QB, and that I promise It'll be a series?
Aahh... I knew you would say yes then! So what are we waiting for, let's get started!

A formatted text viewer can be used in many places: As a help system for your game or program, as a stand-alone one, and... and... eh... a formatted text-viewer. Yes...
Oh whatever, I'm sure you'll come up with something.

In this issue

... I will teach how to do a simple text scroller, with auto-linebreaking of course, and maybe some base formatting if I feel like I want to show you. ;-)

First of all

... You will need something to view: Let's make a .txt file and call it MyBrain.txt, something like this:

this file is called MyBrain.txt. I've 
wanted to call it something longer, 
like TheContentsOfMyBrain.Text. 
But, when Bill Gate$ did DOS (or stole
it, to be more presicely) he didn't
have money for a big hard-drive, there-
fore he allowed the filenames only to 
be 77 Bytes long.

But I'll survive, and here is
the contents of my brain:

---------------------------------------
qMqyw erretlyeugiioopna s?d
fIgth jiksl zWxIcRvAbJnOmDqAw,e rmtyy
uoiwonp aQsBdAfSgIhCj kilnzvxecnvtbendm
qrweelritgyeuoino pwaistdhf gfhojukrl 
zgxocdvsb:n
mWqiwledrCtayrudi o-p aGsoddf gohfj 
kkliznxdcnvebsnsm
qRwaedritoyHuainodpsa s-d fGgohdj 
kolfz xtcuvtbonrmiqawlesr
tJyouridoapna s-d fGgohdj kolfz 
xwcevbbsnimtqewsr
tDyaurikoDparsedafdg h-j kGlozdx covfb 
ncmoqdwienrgt                  
---------------------------------------

Can you break the code of the above,
 and read the real text?
try, cause indeed there is one.

Here's a tip: Each line is encoded
alone but by the same encoding
formula, and the encoding is so simple
that you will laugh, when you guess
it...

Mind you that none of the lines exceeds 40 characters, 'cause our first text-viewer will not support linebreaking.

Now, cut and paste it into notepad, and save it in the same dir as you plan to be coding you text-viewer. From now on, I will be referring to that dir as the viewer dir.

The code

... were we will be using DQB for the ease: We don't need first to make our own font, graphics, and page-flipping routines! YES!

Make sure that you know have a copy of the DirectQb lib, compiled with all routines (for MY ease), in your viewer dir. The viewer dir should now contain the following files: MyBrain.txt, DQB.Lib (if you'd like to compile), DQB.Qlb, and DirectQB.Bi.

Let's get started. First, the DECLARE lines:

'$INCLUDE: 'directqb.bi'
DECLARE SUB DrawScreen (update%)
DECLARE SUB EndProgram ()
DECLARE SUB Init ()
DECLARE SUB LoadFile (File$)

The first line includes the DirectQB include file, with the DECLARE statements for the lib. The next, DrawScreen, will be called to update the screen (to Layer 1, mind you), its argument, update%, tells the sub to, or not to, copy Layer 1 to the real screen. The others should be self-explaining, if you don't think so you will understand the subs later on.

And on we go:

DIM SHARED File(1 TO 2048) AS STRING
DIM SHARED TopLine AS INTEGER
DIM SHARED NumOfLines AS INTEGER

this was the DIM statements, if you haven't figured out...
File(1 to 2048) is the STRING array were we will store the file at run-time.
TopLine tells us what index in the File array that is the topmost one on the screen. Will be needed to know were to start printing, in the sub DrawScreen.
NumOfLines tells us how many lines there is in the file, so we can find were to stop the user from scrolling further.

Ready for more?

Init
LoadFile "MyBrain.txt"
DrawScreen 1

this calls the subs, which will be explained later.

Now, its time for our main loop:

do

It wasn't that tough, hu? he, he... Sorry...

Our key press detection:

     
 if DQBkey(KEYUP) THEN
  if TopLine > 1 THEN TopLine = TopLine - 1: change = 1
 ELSEIF DQBkey(KEYDOWN) THEN
  if TopLine < NumOfLines - 17 THEN TopLine = TopLine + 1: change = 1
 ELSEIF DQBkey(73) THEN
  if TopLine > 15 THEN TopLine = TopLine - 15: change = 1
 ELSEIF DQBkey(81) THEN
  if TopLine < NumOfLines - 17 - 15 THEN TopLine = TopLine + 15: change = 1 
 ELSEIF DQBkey(KEYESC) THEN
  quit = 1
 END if

I know it's not the best way, but it'll work fine here.
Key number 73 and 81 is PgUp and PgDown, respectively.
The quit var is set if we want to quit.
About the change var: if its false, we don't need to update the screen coz nothing has changed. If it is true, then we'll need to update the screen:

 if change = 1 THEN
  change = 0
  DrawScreen 1
 END if

this is pretty simple: if the change var is set, we reset it and update the screen.

Lastly in our main code, we end the main loop and end the program by calling EndProgram:

LOOP UNTIL quit = 1

EndProgram

Of cause the program won't run yet, as we're left to do the subs.

Hu? You want the all of the main code listed now? EASY! You'll get that later...

The subs

... Are coming now. In alphabetic listing:

SUB DrawScreen (update%)

 DQBboxf 1, 0, 0, 319, 10, 1
 DQBboxf 1, 0, 11, 319, 188, 7
 DQBboxf 1, 0, 189, 319, 199, 8

 DQBprint 1, "QFT viewer by Zap/QGZ", CENTERED, 1, 15
 DQBprint 1, "Use arrow keys + PgUp & PgDown to scroll", 1, 190, 7

 x% = 11
 for a = TopLine TO TopLine + 17
  DQBprint 1, File(a), 1, x%, 15
  x% = x% + 10
 NEXT
 
 if update THEN DQBcopyLayer 1, VIDEO

END SUB

Here's an explanation for the DrawScreen sub:

First three lines draws the background, next we print the title (put anything you like there), and then some helpful text at the bottom.

Then we set the start pos for the text (eleven pixels from the top, here), and then print seventeen lines of text.

Lastly, but not less, we update the screen, if we're told to do so, mind you.


We continue:

SUB EndProgram
 DQBclose
 END
END SUB

do I really have to explain this mum? *SIGH*

First we call DQBClose, which closes DQB and removes the keyboard handler.

Next we... Oh forget it! You know what that does, right?!


And we go on:

SUB Init
if DQBinit(1, 0, 0) THEN PRINT "Internal Error, aborting program"
DQBinitVGA
DQBinstallKeyboard

TopLine = 1
END SUB

this sub Installs DQB, VGA screen, and DQB Keyboard handler. It also sets TopLine to one, because the first line is number one...

My fingers are starting to hurt now. Hmm. Next and last sub (so far, anyways):

SUB LoadFile (File$)
OPEN File$ for INPUT AS #1
 do
  linenumber% = linenumber% + 1
  LINE INPUT #1, File(linenumber%)
 LOOP UNTIL EOF(1)
CLOSE
NumOfLines = linenumber%
END SUB           

this loads the file, and sets the NumOfLines var.

So...

... We're done! (With the very simple version...)

Here's the full code (Both the main, as I promised, and a bonus: the subs!...)

'$INCLUDE: 'directqb.bi'
DECLARE SUB DrawScreen (update%)
DECLARE SUB EndProgram ()
DECLARE SUB Init ()
DECLARE SUB LoadFile (File$)

DIM SHARED File(1 TO 2048) AS STRING
DIM SHARED TopLine AS INTEGER
DIM SHARED NumOfLines AS INTEGER

Init
LoadFile "crap.txt"
DrawScreen 1

do

 if DQBkey(KEYUP) THEN
  if TopLine > 1 THEN TopLine = TopLine - 1: change = 1
 ELSEIF DQBkey(KEYDOWN) THEN
  if TopLine < NumOfLines - 17 THEN TopLine = TopLine + 1: change = 1
 ELSEIF DQBkey(KEYESC) THEN
  quit = 1
 ELSEIF DQBkey(73) THEN
  if TopLine > 15 THEN TopLine = TopLine - 15: change = 1
 ELSEIF DQBkey(81) THEN
  if TopLine < NumOfLines - 17 - 15 THEN TopLine = TopLine + 15: change = 1
 END if

 if change = 1 THEN
  change = 0
  DrawScreen 1
 END if

LOOP UNTIL quit = 1

EndProgram

SUB DrawScreen (update%)

 DQBboxf 1, 0, 0, 319, 10, 1
 DQBboxf 1, 0, 11, 319, 188, 7
 DQBboxf 1, 0, 189, 319, 199, 8

 DQBprint 1, "QFT viewer by Zap/QGZ", CENTERED, 1, 15
 DQBprint 1, "Use arrow keys + PgUp & PgDown to scroll", 1, 190, 7

 x% = 11
 for a = TopLine TO TopLine + 17
  DQBprint 1, File(a), 1, x%, 15
  x% = x% + 10
 NEXT
 
 if update THEN DQBcopyLayer 1, VIDEO

END SUB

SUB EndProgram
 DQBclose
 END
END SUB

SUB Init
if DQBinit(1, 0, 0) THEN PRINT "Internal Error, aborting program"
DQBinitVGA
DQBinstallKeyboard

TopLine = 1
END SUB

SUB LoadFile (File$)
OPEN File$ for INPUT AS #1
 do
  linenumber% = linenumber% + 1
  LINE INPUT #1, File(linenumber%)
 LOOP UNTIL EOF(1)
CLOSE
NumOfLines = linenumber%
END sub 

Copy & Paste this into NotePad, and save it as QFTView.bas in your Viewer dir .

Then: RUN IT!

If it works, which it will, it should look like this:

Try scroll up and down with the arrow keys. If you're running on a computer faster then a 386, 16 MHz, you should notice that it is scrolling quite fast. This can be corrected:

Add this line at the top (after the DECLARE's, before the main loop):

CONST ScrollDelay=3

... and this line in the IF block, that updates the screen:

IF ScrollDelay => 1 then DQBWait ScrollDelay

Run it, and change the ScrollDelay to anything that fits on you PC.

The reason why we have to check if the ScrollDelay var is greater than or equal to one, is because if you call DQBWait, which waits for vertical retrace, with eg. a value of 0.3 it'll wait for vertical retrace under 1 times, which is impossible. If you anyway do this, you'll end up with a locked computer.

If it was a non-qb user

He'll rather choose to download another text viewer, than have to download QB, change the vars to fit etc.

Therefore, we'll compile!

Hey! STOP! Don't compile yet. How in the nine hells should the user then open a file? (except the crap one we did before. Oh! BTW have you decoded it? No? SHAME ONE YOU!)

The solve to our problem, is to use the built-in COMMAND$ variable, which returns the parameters shipped with the command-line.

Change this:

Init
LoadFile "crap.txt"

To this:

DIM CurFile AS STRING, ScrollDelay AS INTEGER

dum$ = COMMAND$
IsThere% = INSTR(dum$, "-")

IF IsThere% THEN
 CurFile = LEFT$(dum$, IsThere%)
 arg$ = MID$(dum$, IsThere% + 1, 1)

checkarg:
 SELECT CASE LCASE$(arg$)
  CASE "?"
   CLS
   PRINT "USAGE:"
   PRINT "QFTView -? | FILENAME.TXT [-dSCROLLDELAY#]"
   PRINT
   PRINT "e.g.:"
   PRINT "QFTView somefile.txt -d15#"
   PRINT "Will display the file somefile.txt and set the scrolling delay to 15"
   END
  CASE "d"
   argend% = INSTR(IsThere%, dum$, "#")
   arglen% = argend% - IsThere%
   ScrollDelay = VAL(MID$(dum$, IsThere% + 2, arglen%))
 END SELECT

ELSE
 CurFile = dum$
 ScrollDelay = 0
END IF

LoadFile CurFile
Init

I don't like to explain all this, so if you don't understand it, accept it. I have some small remarks to do, though: The CheckArg: line is for future usage. Make sure that the LoadFile call is BEFORE the Init call, if it's not, and the user gives a wrong filename, he will end up with a locked-up computer (which he will probably throw out the window, and charge you...).

Try compile it, now. If it doesn't work, YOU have probably done something wrong. In case, here's the code:

'$INCLUDE: 'directqb.bi'
DECLARE SUB DrawScreen (update%)
DECLARE SUB EndProgram ()
DECLARE SUB Init ()
DECLARE SUB LoadFile (File$)

DIM SHARED File(1 TO 2048) AS STRING
DIM SHARED TopLine AS INTEGER
DIM SHARED NumOfLines AS INTEGER

DIM CurFile AS STRING, ScrollDelay AS INTEGER

dum$ = COMMAND$
IsThere% = INSTR(dum$, "-")

IF IsThere% THEN
 CurFile = LEFT$(dum$, IsThere%)
 arg$ = MID$(dum$, IsThere% + 1, 1)

checkarg:
 SELECT CASE LCASE$(arg$)
  CASE "?"
   CLS
   PRINT "USAGE:"
   PRINT "QFTView -? | FILENAME.TXT [-dSCROLLDELAY#]"
   PRINT
   PRINT "e.g.:"
   PRINT "QFTView somefile.txt -d15#"
   PRINT "Will display the file somefile.txt and set the scrolling delay to 15"
   END
  CASE "d"
   argend% = INSTR(IsThere%, dum$, "#")
   arglen% = argend% - IsThere%
   ScrollDelay = VAL(MID$(dum$, IsThere% + 2, arglen%))
 END SELECT

ELSE
 CurFile = dum$
 ScrollDelay = 0
END IF

LoadFile CurFile
Init
DrawScreen 1

DO

 IF DQBkey(KEYUP) THEN
  IF TopLine > 1 THEN TopLine = TopLine - 1: change = 1
 ELSEIF DQBkey(KEYDOWN) THEN
  IF TopLine < NumOfLines - 17 THEN TopLine = TopLine + 1: change = 1
 ELSEIF DQBkey(KEYESC) THEN
  quit = 1
 ELSEIF DQBkey(73) THEN
  IF TopLine > 15 THEN TopLine = TopLine - 15: change = 1
 ELSEIF DQBkey(81) THEN
  IF TopLine < NumOfLines - 17 - 15 THEN TopLine = TopLine + 15: change = 1
 END IF

 IF change = 1 THEN
  change = 0
  DrawScreen 1
 
  '/* Scrolling delay */
  IF ScrollDelay >= 1 THEN DQBwait ScrollDelay
 END IF

LOOP UNTIL quit = 1

EndProgram

REM $DYNAMIC
SUB DrawScreen (update%)
 DQBboxf 1, 0, 0, 319, 10, 1
 DQBboxf 1, 0, 11, 319, 188, 7
 DQBboxf 1, 0, 189, 319, 199, 8

 DQBprint 1, "QFT viewer by Zap/QGZ", CENTERED, 1, 15
 DQBprint 1, "Use arrow keys + PgUp & PgDown to scroll", 1, 190, 7

 x% = 11
 FOR a = TopLine TO TopLine + 17
  DQBprint 1, File(a), 1, x%, 15
  x% = x% + 10
 NEXT
 IF update THEN DQBcopyLayer 1, VIDEO
END SUB

SUB EndProgram
 DQBclose
 END
END SUB

SUB Init
IF DQBinit(1, 0, 0) THEN PRINT "Internal Error, aborting program"
DQBinitVGA
DQBinstallKeyboard

TopLine = 1
END SUB

SUB LoadFile (File$)
OPEN File$ FOR INPUT AS #1
 DO
  linenumber% = linenumber% + 1
  LINE INPUT #1, File(linenumber%)
 LOOP UNTIL EOF(1)
CLOSE
NumOfLines = linenumber%
END SUB

Copy & Paste this into NotePad, save it as QFTView.bas in your Viewer dir, open it in QB 4.5, compile it, and run it.

Try it with different parameters. You might notice that if you run it with a wrong filename, it'll come up with a strange error.

Now you expect me to correct it right? NO! This is an EXERCISE for YOU to make.

Hmm... why not end with a little competition? Here:

  • Make the program show a FRIENDLY error msg when run with a wrong file name.
  • Send it to RuneMoennike@hotmail.com
  • The best entry (Friendliness and best & future friendly coding) will be shown in the next part of this article (in the next issue of QBCM).

    So... Guess this is the end of this part... *SNIF* ... Well... Mail me with any complaints, greets, ideas for next part (have some myself, but more's needed), or anything else about this article. You could of course also go to the QbasicNews Forum and post there.

    THE END OF THIS PART

    PS: Yes, I know I didn't teach any formatting this issue, but after writing 500 lines of HTML code, I decided that it would just have to wait.

    'Till next time, Zap.