macdevcenter.com
oreilly.comSafari Books Online.Conferences.

advertisement

AddThis Social Bookmark Button

REALbasic for HyperCard Users
Pages: 1, 2, 3

In HyperCard the difference between the index card and the entry cards is expressed as two different backgrounds. In REALbasic, there is no card/background metaphor, so we need two different windows. Let's call these "Index" and "Entry". To be more HyperCard-like, we can make these behave like a single window by having them appear only one at a time in the same position on the screen. So Index has a method doshow:



  if not self.visible then
    self.top = entry.top
    self.left = entry.left
    self.show
    entry.hide
  end

Entry has a method doshow as well, which is parallel. REALbasic doesn't easily support HyperCard-like visual effects during the transition between cards, so we won't have any.

HyperCard is natively a text database, where storage and retrieval of data is handled automatically and transparently; thus the entries are just different cards of the second background, where showing a card means showing its data automatically. To navigate from entry 573 to entry 574, for example, we simply "go next card."

In REALbasic none of that is true! There are no cards -- to show different entries we must replace the data displayed in the Entry window with other data. Thus, we must implement storage of the data in such a way that for any entry we can retrieve the data on demand. And when we retrieve new data, we must manually repopulate the Entry window.

To solve this problem, let's take advantage of the fact that REALbasic provides a native "database" file format, along with methods to access the data. This isn't as fast or compact as HyperCard's built-in storage, and of course it's nowhere near as automatic, but it will prove adequate to our purposes. So, unlike HyperCard (where the code and data together make up a stack), in REALbasic the data will live in a separate file and our application will just be a way of viewing it.

Constructing the REALbasic application

The first step is to build the database file, which we'll call rogetdb. I start by exporting the entry text from the HyperCard stack as a series of styled text files, because the entries have styling I want to preserve. The files are named "roget1", "roget2", and so forth. Because the cards are exported in order, the numeric part of these names can be used as each entry's index value.

In the REALbasic IDE, I choose "New REAL Database" to create the database file and double-click the database entry in the Project Window to edit its schema. This consists of a single table called "data". REALbasic's database file format has no provision for storing styled text, so I need to hold the text information and the style information in two different fields, which I call txt and styl. I also add a numeric field num to hold the index value. I write and run a routine to open each styled text file in turn, capturing its text and style information, along with the index value from the file's name, and adding a record to the database containing this information.

Next, I export the HyperCard stack's class, section, and part headings -- along with the information from the top line of the entry -- as a tab-delimited and return-delimited text file. In REALbasic, I add class, section, part, and numword fields to the database file's schema, and run code to import the data by parsing the text file. I'm not showing you any of that code, because it isn't part of the final project. Once the database file is built, the code that built it can be thrown away! Besides, all this isn't really so different from how you'd initially construct a HyperCard stack.

Now we are ready to create our actual REALbasic application. The first thing it must do is link up with its data. We assume that rogetdb is in the same folder as our application. Since this linkage belongs to the application as a whole, we create an application subclass and give it a database property called d. In the application subclass's open event, which is the first code executed as the application starts up, we put this code:

self.d = openrealdatabase(getfolderitem("").child("rogetdb"))

Now let's consider how to navigate among entries. Only the Entry window needs to do this, so we give it a database cursor property called c and a method dodisplay. The idea of this architecture is that any routine can set c to point to the desired record, and call dodisplay to show it.

Manipulation of c will be performed using a SQL select command to obtain a virtual table of records, and possibly the movenext command to cycle forward through the virtual table, until the c property's current record is the right one. Here is the dodisplay code:

  txt.text = ""
  txt.settextandstyle 
    c.field("txt").stringvalue, c.field("styl").stringvalue
  txt.sellength = 0
  txt.selstart = 0
  class.text = c.field("class").stringvalue
  section.text = c.field("section").stringvalue
  part.text = c.field("part").stringvalue
  numword.text = c.field("numword").stringvalue
  idx = c.field("num").integervalue
  doshow

class, section, part, and numword are the StaticText controls displaying the headings at the top of the window; txt is the EditField displaying the text of the entry.

idx is an integer property of the Entry window. Its purpose is to make it easy to move to the next or previous record. For example, here is the code for the method gonext which is called by the action event handler of the Next button:

  c = app.d.sqlselect(
    "select * from data where num=" + str(idx+1))
  if c.eof then
    beep
    return
  end
  dodisplay

That's our equivalent of HyperCard's "go next." It's a good illustration of how c and dodisplay are intended to be used. The code for goprev is completely parallel.

When the user clicks a heading, the display should cycle forward to the first entry that has a different heading in that category. In HyperCard, the way I did this for, say, the class heading was to give the class field this mouseup handler:

  get me
  lock screen
  repeat while it is bg fld "class"
    set cursor to busy
    go next cd of this bg
  end repeat

The REALbasic approach is almost exactly the same. All three headings work the same way, so we move the common code out into a method called nextdifferent, which takes two string parameters -- the current value displayed by the particular static text, s, and the name of the database field to look in, fld. Here is the nextdifferent method:

  c = app.d.sqlselect(
    "select * from data where num > " + str(idx))
  while (not c.eof) and c.field(fld).stringvalue = s
    c.movenext
  wend
  dodisplay

To illustrate, here is the class element's mousedown event handler:

  nextdifferent me.text, "class"

Now let's talk about what happens when the user clicks in the text of an entry. Recall that if the user clicks in a number we want to navigate to that card; otherwise we want to find the word clicked in. In HyperCard, here's the code I was using in the txt field's mouseup handler:

  put the clicktext into what
  if what is a number then
    find what in bg fld "numword"
  else
    put "find word" && quote & the clicktext & quote 
        && "in bg fld " & quote & "txt" & quote
    returnkey
    returnkey
  end if

This relies on two things we get for free in HyperCard: the clicktext property, and the ability to "find." But we don't get either of those in REALbasic! Thus we must implement them ourselves. So the first thing that the txt EditField's mouseup event handler must do is to work out the "click text." To do this, I start with the point of the selection and expand in both directions until I hit a character that isn't a word character:

  dim sel as integer
  dim first,last as integer
  dim s as string
  dim acc as string
  dim r as regex
  dim m as regexmatch
  acc = "abcdefghijklmnopqrstuvwxyz1234567890"
  sel = me.selstart
  s = me.text
  first = sel+1
  last = first
  do until instr(acc, mid(s,first,1)) = 0
    first = first - 1
  loop
  do until instr(acc, mid(s,last,1)) = 0
    last = last + 1
  loop
  s = mid(s, first+1, last-first-1)

Now s contains the click text, and we need to search for it. If s is a number, the REALbasic approach is very like HyperCard's:

if str(val(s)) = s then
 c = app.d.sqlselect(
   "select * from data where numword like '" + s + ".%'")
 dodisplay
 return true

In HyperCard, if s is text, recall that we simply use "find word" to cycle through every entry looking for a word consisting of the text we specify. In REALbasic, though, we must implement this ourselves.

To handle the fact that the search is for a word, we'll take advantage of REALbasic's new regular-expression support. To handle the fact that the search is from card to card, we rely on a utility method, canfind, which accepts a regular expression and a SQL command specifying a set of entries to cycle through, and calls dodisplay if the search succeeds. We look first in the same entry we're already in; if we don't find the text, we look in the entries that follow it; if we still don't find the text, we look in the entries that precede it. Finally, if we've found the text, we select it.

else
  r = new regex
  r.searchpattern = "\b" + s + "\b" // whole word
  r.options.casesensitive = false
  m = r.search(me.text, last)
  if m = nil then // try subsequent
    m = canfind(r, "select * from data where num>" + str(idx))
    if m = nil then // try previous
      m = canfind(r, "select * from data where num<" + str(idx))
      if m = nil then
        beep // not found
        return false
      end
    end
  end // found it, mark it
  me.selstart = m.subexpressionstart(0)
  me.sellength = last-first-1
end

Here is the canfind code (r and s are the incoming regex and string parameters):

  dim m as regexmatch
  c = app.d.sqlselect(s)
  while not c.eof
    m = r.search(c.field("txt").stringvalue,0)
    if m <> nil then
      dodisplay
      return m
    end
    c.movenext
  wend
  return nil

Pages: 1, 2, 3

Next Pagearrow