Protuberance Interface v1.0 Freeware

Fomalhaut Software 2002-2003

Tutorial

In the tutorial we'll make a program named "Vector Graphic Editor" step-by-step. This will explain the whole process of creation the program with Protuberance very particularly

Contents

  • What it'll be?
  • Starting
  • Making a button
  • Making a message, naming the button
  • Setting the tag
  • Programming simple button
  • Programming button group
  • Programming flip button
  • Making button "hard to press"
  • Picture storing system setup
  • Making a window
  • Programming a window
  • Programming selection
  • Programming other stuff
  • Making a list
  • Programming a list
  • Making inputfield with +/- buttons
  • Dragging event
  • Save / load dialog
  • Adding F1 help, getting sub from ADVANCED.BAS module
  • Different grid size

    What it'll be?

    It will be a simple vector pictures editor with following features: drawing different - colored primitives (line, triangle, rectangle, circle), save/load dialog.

    Starting

    Select 800x600 resolution and 8bit color depth. Type in project name inputbox "VGEDIT". Press "New project". Choose "Selectable" screen mode template. Establish the project in Protuberance directory - click on "Proceed". Don't modify the parameters yet.

    Making a button

    Switch to page editing with . First button we'll make will be exit button. Press RMB on bottom-right corner of blank page (scroll it down with MMB or "`" key). Make the box by dragging the mouse in up-left direction then releasing button. If box is not exactly in corner - move it with LMB key. Stretch box if necessary with RMB key.

    Making a message, naming the button

    Now let's make our first message. Press "New messages" button (text will appear), select long bar and type-in "Exit" then press 'enter'. If the box was selected then it automatically will be titled. Otherwise, select the box and press "+" near "Message" inputbox. You can switch to "Message edit" mode and edit previously entered messages. Set the shape of a button with "Image" inputbox (1:0) and font with Font inputbox (1).

    Setting the tag

    To set the tag (button ID) you may enter its value in "Tag" inputbox or press "+/-" buttons. Note that every created box has the tag equal to its parent's + 1. Set tag of the box equal to 199.

    Loading our interface

    Now save our work with button, then exit using . To lauch our project we need QBasic (QB.EXE) and Future Library (PFL.QLB, FUTURE.BI - you can copy them from Protuberance directory). How to set pats was explained above, so go to the project's directory (VGEDIT) and enter in command line "QB /LPFL.QLB". Then load "VGEDIT.BAS", our project's main file. You may make new Future Library using only components needed for the project, then rename library files to in example "VGEDIT.LIB/QLB", put them to the project's directory and lauch QB with "QB /LVGEDIT".

    Programming simple button

    Press F2 and select BTPRESS procedure in PROGRAM module. You'll see some code including SELECT CASE tag / END SELECT block. Don't modify existing code, just add yours. To make button operational we need to enter string between these lines:

    SELECT CASE tag
     CASE 199
      shellexit
    END SELECT

    CASE 199 in BTPRESS means "in response of pressing button with tag 199 do ...". SHELLEXIT simly shuts down the program. Save the program and lauch it. You'll encounter laucher page and can change resolution and color depth. Try to press exit button. You also can exit by pressing ESC at any time.

    Programming button group

    Return to Protuberance Editor and load your project (it now appeared in recent projects section). Create 5 buttons with tags 100,101...104 and labels "Vertex","Line","Triangle","Polygon","Rectangle","Circle". You can select several boxes by dragging LMB, add new boxes to selection with CTRL. Save all and load the project in QB IDE. To make button group operational you'll need to add a SHARED variable. Add following line to the end of "PROGRAM.BAS" main module:

    DIM SHARED tool

    This variable will store pressed button number. If one of other buttons in a group being pressed, old pressed button will unpress. So, go to BTPRESS sub and enter these strings in SELECT CASE block:

     CASE 100 to 104
      tool = tag - 100
      boxgupdateb 100, 104

    Here we sets new tool (0 for tag 100 ... 4 for tag 104) if user pressed one of the buttons in [100,104] tag interval - it's our group. Then, program updates all these buttons (checks them for changes and redraws if necessary). Now it's time to modify BTSTATE function - it also has SELECT CASE block. Add there some lines of code:

     CASE 100 to 104
      s = tool + 100

    It means: if tag of the button is in [100,105] interval and equal to tool+100 then button is pressed otherwise not. The s subvariable is intended for comparing with tag - if it is equal to tag, the button will be pressed. Now press F5 and test button group.

    Programming flip button

    Place new button under those 5 in Editor and label it "Chained". Set its tag to 109. This button must be pressed after first click and unpressed after second click. Exit, lauch QB and add some lines of code:

    PROGRAM.BAS module (add to the end)

    DIM SHARED chained

    Here we declare variable that will hold our option (is can be FALSE(=0) or TRUE(=1)).

    btpress SUB (to SELECT CASE block)

     CASE 109
      chained=1-chained

    Here this variable "flipping" between 0 and 1 after click.

    btstate SUB (to SELECT CASE block)

     CASE 109
      p=chained

    It means: if tag of the button is 109 and chained is TRUE then button is pressed otherwise not. Generally, if p ≠ 0 then button will be pressed. Try to run your prog and press the button

    Making button "hard to press"

    User may press "Exit" button by accident. Let's make this button "hard to press" - user must press both mouse buttons to exit. Change "shellexit" line in BTPRESS Sub to this:
      IF mb = 3 THEN
       shellexit
      ELSE
       dragset 0, 0, 0
      END IF

    Picture storing system setup

    Picture will consist of several arrays: vertexes, primitives color, vertexes and type. Vertex is simply a point on workarea. Primitive is a simple figure that have color and based upon 2 or 3 vertexes (in example, triangle is based on 3 vertexes, circle - on 2 - center and point on the edge). We need following TYPE, CONSTants and arrays (enter these strings after sub declarations in PROGRAM.BAS module:

    TYPE vertex
     x as single
     y as single
    END TYPE

    CONST vertmax=100, figmax=100

    DIM SHARED vert(vertmax) as vertex, figcol(figmax), figvert(figmax, 1 TO 3), figtype(figmax)
    DIM SHARED vertq, figq

    Constants will ease future changes. vertq and figq is current quantity of the vertexes and figures.

    Making a window

    Lauch editor. Switch to windows editing using button. Create window with and modify parameters (X,Y): Magn(800,600), Max(6400,4800), Min(400,300). Switch to page editing, make large box and set its type to window , tag to 2 (to attach window that we just created to the box).

    Programming a window

    Now lauch QB IDE and make some changes of some subs:

    wndpress SUB (to SELECT CASE block)

     CASE 2
      vertq = vertq + 1
      scrtownd 2, mx, my, vert(vertq).x, vert(vertq).y
      wndrefreshtagb 2

    This piece of code adds vertex when user presses mouse button on window: mouse cursor coords will be translated to windows grid coordinate system and stored in vertex array at once. wndrefreshtagb sub will redraw window after this.

    wndprepare SUB (to SELECT CASE block)

     CASE 2
      Col& = colfind(0, 0, 0)

    We set Col& variable to 0 for filling window with color 0 before refreshing.

    wnditemshow SUB (to SELECT CASE block)

     CASE 2
      FOR n=1 TO vertq
       wndtoscr 2, vert(n).x, vert(n).y, sx, sy
       future.circle sx, sy, 2, 15
       future.line sx - 4, sy, sx + 4, sy, 15, -1
       future.line sx, sy - 4, sx, sy + 4, 15, -1
      NEXT n

    These lines displays all the vertexes on screen: wndtoscr translates vertex coords (in windows coordinate system) to the screen coords (in pixels). Viewport controller (for avoiding graphics to be drawn outside the window) is already in the sub. Now try creating vertexes and transforming window (change view).

    Programming selection

    Let's make vertex selection possible. Do these changes:

    PROGRAM.BAS module (ADD to the end)

     DIM SHARED selvert

    This SHARED variable is for currently selected vertex

    moveover SUB (to SELECT CASE block)

     CASE 2
      FOR n=1 to vertq
       wndtoscr 2,vert(n).x,vert(n).y,x,y
       IF ABS(x-mx)+ABS(y-my)<=3 THEN
        IF selvert<>n then
         selvert=n
         mouseoff
         wndrefreshtagb 2
         mouseon
        END IF
        EXIT SUB
       END IF
      NEXT n
      IF selvert>0 THEN
       selvert=0
       mouseoff
       wndrefreshtagb 2
       mouseon
      END IF

    This block of code checks all vertexes on being nearly the mouse cursor (current "sensitivity" distance is 3 pixels, calculation is rough but accurate enough). Vertex window coordinates will be translated to the screen coords and compared with mose cursor coords. If vertex is near enough then it will be selected and screen will be redrawn. Last IF..THEN clears selection if no vertexes is near the mouse cursor.

    wnditemshow SUB (change previously entered SELECT CASE block)

     CASE 2
      FOR n=1 TO vertq
       wndtoscr 2, vert(n).x, vert(n).y, sx, sy
       IF n=selvert THEN col=13 ELSE col=15
       future.circle sx, sy, 2, col
       future.line sx - 4, sy, sx + 4, sy, col, -1
       future.line sx, sy - 4, sx, sy + 4, col, -1
      NEXT n

    This changed block will display the selected vertex with different color. Lauch program and try selecting vertexes.

    Programming other stuff

    PROGRAM.BAS module (add to the end)

     DIM SHARED vertnum

    programinit SUB (add to the end)

    vertnum=1

    This variable is for vertex number in figure (1-3) user currently choosing. We also need to set its value to 1 (currently selecting 1st vertex).

    wndpress SUB (change previously entered SELECT CASE block)

     CASE 2
      IF mb=2 THEN
      
    ' If user pressed right mouse button then ..
       IF tool=0 AND selvert>0 THEN
        vert(selvert)=vert(vertq)
        n = 1
        DO
         FOR nn = 1 TO 2 - (figtype(n) = 2)
          IF figvert(n,nn)=selvert THEN
           figcol(n)=figcol(figq)
           figvert(n,1)=figvert(figq,1)
           figvert(n,2)=figvert(figq,2)
           figvert(n,3)=figvert(figq,3)
           figtype(n)=figtype(figq)
           figq=figq-1
           n = n - 1
           EXIT FOR
          END IF
          IF figvert(n,nn)=vertq THEN figvert(n,nn)=selvert
         NEXT nn
         n = n + 1
         IF n > figq THEN EXIT DO
        LOOP
        vertq = vertq - 1
        selvert=0
       
    ' If user is working with vertex tool then this block deletes selected vertex by moving last vertex
       
    ' to its position. The loop is for correcting errors (following this moving) in figure vertexes array.
       ELSE
        vertnum=1
       
    ' Otherwise, vertex selection for figure will be resetted
       END IF
      ELSE
       IF selvert=0 THEN
        vertq = vertq + 1
        scrtownd 2, mx, my, vert(vertq).x, vert(vertq).y
        selvert=vertq
       END IF
      
    ' This block adds vertex if no one is selected

       IF tool=0 THEN EXIT SUB
      
    ' Tool 0 just creates vertexes

       FOR n=1 TO vertnum-1
        IF figvert(figq+1,n)=selvert THEN EXIT SUB
       NEXT n
      
    ' Checking if this vertex is already selected for this figure

       figvert(figq+1,vertnum)=selvert
      
    ' Including this vertex to the figure's vertexes

       IF tool = 2 THEN maxvert = 3 ELSE maxvert = 2
      
    ' Triangle tool requires 3 vertexes, others - 2

       IF vertnum=maxvert THEN
        figq=figq+1
        figcol(figq)=63
        IF chained THEN
         IF tool = 2 THEN
          figvert(figq + 1, 1) = figvert(figq, 1)
          figvert(figq + 1, 2) = figvert(figq, 3)
         ELSE
          figvert(figq + 1, 1) = figvert(figq, 2)
         END IF
        ELSE
         vertnum = 1
        END IF
        figtype(figq) = tool
       
    ' "Chained" option includes previously selected vertexes in next figure.
       ELSE
        vertnum=vertnum+1
       END IF
      
    ' If this vertex is last, finish the figure, else user selects next
      END IF
      wndrefreshtagb 2

    wnditemshow SUB (change previously entered SELECT CASE block)

     CASE 2
      FOR n=1 TO figq
       v=figvert(n,1)
       wndtoscr 2, vert(v).x, vert(v).y, x1, y1
       v=figvert(n,2)
       wndtoscr 2, vert(v).x, vert(v).y, x2, y2
       col=figcol(n)
       SELECT CASE figtype(n)
        CASE 1
         future.line x1, y1, x2, y2, col,-1
        CASE 2
         v=figvert(n,3)
       wndtoscr 2, vert(v).x, vert(v).y, x3, y3
         future.trifill x1, y1, x2, y2, x3, y3, col
        CASE 3
         future.fillbox x1, y1, x2, y2, col
        CASE 4
         future.fillcircle x1, y1, SQR(0& + 1& * (x2 - x1) * (x2 - x1) + 1& * (y2 - y1) * (y2 - y1)), col
       END SELECT
      NEXT n
      FOR n=1 TO vertq
       wndtoscr 2, vert(n).x, vert(n).y, sx, sy
       IF n=selvert THEN col=13 ELSE col=15
       FOR k=1 to vertnum-1
        IF figvert(figq+1,k)=n THEN col=64
       NEXT k
       future.circle sx, sy, 2, col
       future.line sx - 4, sy, sx + 4, sy, col, -1
       future.line sx, sy - 4, sx, sy + 4, col, -1
      NEXT n

    We have just added highlight for current figure vertexes and drawing of the finished figures. Notice LONG numbers (0&, 1&) in CASE 4, they're for overriding Overflow error - all calculations are in LONGs.

    Making a list

    Now let's make palette selector. We need a window for holding 256 items. Lauch editor and switch to window editing. Press "Create window" button. Set parameters: Magn(16,16), Max(50,50), Min(2,2), Items(256), Arrangement . Go to page editing and create window between buttons. Change its tag to 3. You just created a 256 item ('cause there's 256 colors in the palette) list with variable item (color rectangles) size (2-50) and auto-arrangement.

    Programming a list

    PROGRAM module (add to the end)

     DIM SHARED selcol

    This variable is for storing currently selected color to apply to new figures.

    wndpress SUB (add another CASE in the SELECT CASE block)

     CASE 3
      selcol=v-1
      wndrefreshtagb 3

    There number of item under mouse cursor is already calculated - v. But it's in range of (1-256) and color is in (0-255). So, program will set current color as current item minus 1.

    wnditemshow SUB (add another CASE to the SELECT CASE block)

     CASE 3
      future.fillbox x1, y1, x2, y2, num-1
      nn=selcol+1

    First line displays color rectangle, second activates 'selector'. It's dotted rectangle that will be drawn over rectangle with number stored in 'nn' variable. For this example, the number of selected color box is in 'selcol'+1.

    wndpress SUB

     modify the string
      figcol(figq) = 63
     to
      figcol(figq) = selcol

    Making inputfield with +/- buttons

    Switch to the editor and reduce Y-size of the palette window. In just appeared free space create the box, switch its mode to and select 3th image for it. Also set the maximul length of symbols for this inputbox to 3. Create 2 small buttons near it like as near editor inputfields. Change their image to (1:0), mode to , label to (7,0 and 1). Set the inputbox tag to 110, small button tags to 111 and 112 (notice, for further addition of analogous things, inputfield's tag must be 5n, sm.b. tags must be 5n + 1, 5n + 2, n is integer)

    valueget SUB (add CASE to the SELECT CASE block)

     CASE 110
      vi = selcol

    Here we just programming selected color value to be shown in the box

    valueset SUB (add CASE to the SELECT CASE block)

     CASE 110 to 112
      selcol = bounds (selcol * vg + vi, 0, 255)
      wndrefreshtagb 3

    And here's very interesting method to attach +/- buttons to the inputfield. When user pressed "+/-" button, vg will be 1 and vi will be increment / decrement (in example, +1 or -10). So, out value will be old value (selcol) plus inc / dec. If user just typed value in inputfield, vg will be 0 and vi will be this newly typed value, so selcol will change to vi. Bounds function is for keeping value in the range, so 0 >= selcol >=255. If you don't want to use "+/-" buttons, you may change the string to "selcol = vi".

    Well, here's the formula: VARIABLE = bounds (VARIABLE * vg + vi, VMIN, VMAX).

    valueset SUB (add CASE to the SELECT CASE block)

     CASE 111 to 112
      valueset w,""
      boxdrawtagb 110
      wndrefreshtagb 3

    Valueset command will modify the selcol value, boxdrawtagb will refresh the inputfield and wndrefreshtagb will repaint palette window, so the palette cursor will jump to right position. We also need to redraw inputfield if user changes the color by a window:

    wndpress SUB (add to end of CASE 3 block)
     boxdrawtagb 110

    Dragging event

    We've done much, but editor still miss one feature: ability to move vertexes. It can be implemented by the following way:

    wnddrag SUB (add new CASE block)
     CASE 2
      IF selvert > 0 AND tool = 0 AND mb = 1 THEN
       scrtownd 2, mx, my, vert(selvert).x, vert(selvert).y
       wndrefreshtagb 2
      END IF

    Just vertex tool with left mouse button can move vertexes. But when the redrawing of a window becomes slow (in more complex editors and drawings) the second variant comes in handy.

    wnditemshow SUB (add to the end of CASE 2 block)
     wndget w

    With wndget we store the newly refreshed window in the 2nd screen buffer.

    wnddrag SUB (add new CASE block)
     CASE 2
      IF selvert > 0 AND tool = 0 AND mb = 1 THEN
       IF mode = 1 THEN
      
    ' Mode 1 means "during drag'
        wndput w
        wndviewportset w, 0, 0, 0, 0
        outputset 0
        dotline x, y, mx, my, 21845
        scrtownd 2, mx, my, vert(selvert).x, vert(selvert).y
        bufput 0
        screenviewportset
       ELSE
      
    ' Mode 2 means "after drag'
        wndrefreshtagb 2
       END IF
      END IF

    This pack'o'commands in "mode 1" restores previously refreshed window by putting 2nd screen buffer to 1st screen buffer, sets 2th wndow viewport, then draws line, that indicate old and new position of the vertex, then puts it all to the screen and restores default viewport.

    Save / load dialog

    With Protuberance managing disk i/o dialog is damn easy! Just add the button to the main window, label it "Load&Save", set tag to 120 and add these strings to the program:

    btpress SUB (to SELECT CASE block)

     CASE 120
      pageset 3
     CASE 987
      pageset 1

    First CASE is for setting dialog page, second for returning to main window. Fill the FILESAVE and FILELOAD SUBs:

    fileload SUB

    openforloading filename$
     GET #1, , vertq
     GET #1, , figq
     FOR n = 1 TO vertq
      GET #1, , vert(n)
     NEXT n
     FOR n = 1 TO figq
      GET #1, , figtype(n)
      GET #1, , figcol(n)
      FOR k = 1 TO 3
       GET #1, , figvert(n, k)
      NEXT k
     NEXT n
    CLOSE #1

    filesave SUB

    openforsaving filename$
     PUT #1, , vertq
     PUT #1, , figq
     FOR n = 1 TO vertq
      PUT #1, , vert(n)
     NEXT n
     FOR n = 1 TO figq
      PUT #1, , figtype(n)
      PUT #1, , figcol(n)
      FOR k = 1 TO 3
       PUT #1, , figvert(n, k)
      NEXT k
     NEXT n
    CLOSE #1

    Adding F1 help, getting sub from ADVANCED.BAS module

    Open your project in QB IDE, load ADVANCED.BAS module using "Load file" command. Move QHELP sub from it to "ADMODULE.BAS" (if you'll want to add other subs later, some of them must be in main module). Now save all and add some lines of code:

    keypress SUB (to SELECT CASE block)

     CASE -&H3B
      qhelp "DATA\f1help.txt"

    &H3B - scancode of "F1" key, minus means that it's scancode. QHELP sub displays a text file and waits for a key. Copy "f1help.txt" file from "DATA" directory of PROTUBERANCE to same directory of VGEDIT. Modify it (delete last paragraph), then try to press F1 in program.

    Different grid size

    There's a small problem last: we have pages with 64x48 grid, they fits perfectly in 640x480, 1024x768 resolutions, but in 320x200 there's two wide bars of unused space on the left-right sides. So, it's better to use pages with 64x40 grid in this case. Load the project in editor and modify the page, freeing 8 bottom rows (stretch the windows and move Exit button and color inputfield up). Save the pages as "VGEDIT2.PPG". Add these lines:

    programlauch SUB (replace string "pageload comdir$ + pagefile$" with)

     IF syres / sxres >= .75 THEN
      pageload comdir$ + pagefile$
     ELSE
      pageload "DATA\VGEDIT2.ppg"
      gysiz = 40
      sqsxpypcalc
     END IF

    Code loads alternative pages and modify grid size if the y resolution is less than 3/4 of x resolution.