| |||||||||||||||||||||||||||
|
| |||||||||||||||||||||||||||
Print This Article REALbasic University: Column 060
Monotab: Part IMy favorite thing about REALbasic is the way it incorporates into my thinking. Whenever I have a problem to solve, even a trivial one, I turn to REALbasic. It's like a best friend that's always around to help. But I must be alert to opportunties for REALbasic to help me. Any time I do anything repetative I ask myself, "Can I write a program that will make this task easier?" One task that has cropped up from time to time is that I'll have some tabular data in a tab-deliminated format and I need to convert it to a format suitable for emailing. (This often happens when I copy data from a table on a web page.) Since most email programs are text-only, a tab-deliminated table will appear something like this (data courtesy Major League Soccer): Team GP W L T Pts GF GA San Jose Earthquakes 17 10 5 2 32 27 16 Dallas Burn 16 7 3 6 27 24 19 Colorado Rapids 17 8 8 1 25 24 28 New York/New Jersey MetroStars 16 7 7 2 23 24 21 Los Angeles Galaxy 16 7 7 2 23 20 22 Kansas City Wizards 16 5 5 6 21 21 22 Chicago Fire 15 6 7 2 20 24 22 D.C. United 15 6 8 1 19 18 22 New England Revolution 16 6 9 1 19 27 31 Columbus Crew 16 5 8 3 18 21 27 Eek! That looks awful and is nearly impossible to read. (You can't even tell my Quakes are leading the league! ;-) But if we convert those tabs to spaces and displayed the table in a monospaced font, we have a text-only table that's easy to read. Unfortunately, that's a tediously mind-numbing task do by hand, and most of us wouldn't bother. Fortunately, I'm the kind of person that's picky about how emails are formatted, and I have REALbasic to assist me.
Planning MonotabFor today's project, we're going to create a program that will convert a tab-deliminated file into a text file where each piece of text is separated by the appropriate number of spaces. That last condition is what makes a simple procedure complicated, as the number of spaces are potentially different for each bit of text. Before we begin programming, let's do a little thinking about exactly how Monotab will work. First, we need a way to input a text file into our program: I suggest a simple drag-and-drop interface rather than mess with an awkward "Open Document" dialog. Second, let's think about how we'll actually handle the conversion. What algorithm will we use? We obviously cannot simply replace tab characters will a set number of spaces since the number of spaces change depending on the length of the text. For example, in the sample text displayed earlier, we have a list of Major League Soccer teams and their current standings in the league. But since the team names are of different lengths, the number of spaces after the team name changes if we want the next column to start at the same place. Here's what that table looks like when we replace each tab with three spaces: Team GP W L T Pts GF GA San Jose Earthquakes 17 10 5 2 32 27 16 Dallas Burn 16 7 3 6 27 24 19 Colorado Rapids 17 8 8 1 25 24 28 New York/New Jersey MetroStars 16 7 7 2 23 24 21 Los Angeles Galaxy 16 7 7 2 23 20 22 Kansas City Wizards 16 5 5 6 21 21 22 Chicago Fire 15 6 7 2 20 24 22 D.C. United 15 6 8 1 19 18 22 New England Revolution 16 6 9 1 19 27 31 Columbus Crew 16 5 8 3 18 21 27 Yuck! So in order to display our table correctly, we must do some calulations and figure out the appropriate number of spaces for each field. There are two calculations that must be done. First, we must figure out where each column must start. That's more difficult than it sounds as we'll see in a minute. Second, we must calculate the number of spaces for each line of each field. Let's look at the first few lines in the example to see how this works. Team GP W L T Pts GF GA San Jose Earthquakes 17 10 5 2 32 27 16 Dallas Burn 16 7 3 6 27 24 19 Colorado Rapids 17 8 8 1 25 24 28 New York/New Jersey MetroStars 16 7 7 2 23 24 21 To align "GP" so it's further right than the end of "San Jose Earthquakes" we must put at least 17 spaces after "Team." However, it's easy to see that since "New York/New Jersey MetroStars" is even longer than "San Jose Earthquakes" even 17 spaces aren't enough! That should tell us that in order to calculate the minimum number of spaces between fields, we must first discover the widest data within the current field (in this case, the "New York/New Jersey MetroStars" phrase). However, you'll see that even when we do that, it isn't always enough. Sure, the first two columns are aligned, but for the wide column, there isn't much breathing room between the first two columns: Team GP W L T Pts GF GA San Jose Earthquakes 17 10 5 2 32 27 16 Dallas Burn 16 7 3 6 27 24 19 Colorado Rapids 17 8 8 1 25 24 28 New York/New Jersey MetroStars 16 7 7 2 23 24 21 Wouldn't it be nice to have a minimum amount (like three spaces) between each field? Even better, why not allow the user to dynamically specify this amount? That leads us to our third question: what kind of interface does Monotab need? Obviously, this is a program for internal use: it isn't going to be sold or distributed, so it doesn't need much beyond the bare essentials. It could even not have any interface at all and be run as a command-line program (i.e. drop a file onto the Monotab's icon and it converts it and quits)! However, since this is REALbasic and even bare bones interfaces are simple, we'll give our program an interface. My first thought for an interface is that since this is a program working with tabular data, it makes sense to organize the data inside a listbox, with one row of data for each line in the text, and a column for each field. That way the user can check the data to make sure it was parsed correctly before converting it. To set our "minimum spaces" amount, we'll use a slider control. Before we create Monotab, we need a test file we can use to make sure our program is working correctly. Prior to the competition, I used Monotab to generate a text-only World Cup soccer TV schedule which I emailed to friends and family. Here's the original tab-deliminated file you can use for testing Monotab (you should Option-click on the link to download the file, otherwise your web browser will display the file).
Setting up the InterfaceCreate a new blank project file in REALbasic (launch RB and choose "New" from the File menu). Double-click on the Window1 window created by default and attempt to make it look something like this: ![]() That's a listbox at the top. Its properties don't matter too much, since we'll change most dynamically as the program runs, but it would be a good idea to set all the "lock" properties (lockLeft, lockRight, etc.) so that the listbox's size will change if the user enlarges the window. The slider control is named padSlider and has maximum set to 25 and value set to 3. The pushButton is called exportButton. Other than those properties, the defaults should work for everything else. StaticText1 is a textual indicator of padSlider's current value: so we just need to put the following code in its Open and ValueChanged events: staticText1.text = str(me.value) + " spaces" If you've set padSlider to update dynamically (liveScroll is true), the staticText will be redrawn as the user slides the control. Next, we need to add the ability to accept a dropped file. As usual, this is a two-step process. First, we must tell REALbasic about the kind of file we'll accept. In this case, that's a text file. So go to the Edit menu and choose "File Types." There you'll add a new type (click the "Add..." button) and give it settings like this: ![]() Second, we've got to tell listBox1 to accept files of this type. Within the Open event of listBox1 put this code: me.acceptFileDrop("text")
Perfect! Now any text files dropped on listBox1 will be sent to the DropObject event. Next week we'll handle parsing those files and write the code that does the actual conversion. If you would like the complete REALbasic project file for this week's tutorial (including resources), you may download it here.
SimpleEtchI previously promised a special version of our SimplePaint project, so I'll briefly present that here. The idea is to modify SimplePaint into SimpleEtch, a virtual Etch-a-Sketch drawing program. If you remember using an Etch-a-Sketch as a child, you'll remember that once you put the "pen tip" down, you cannot lift it up and move the cursor without drawing. Thus all Etch-a-Sketch drawings are made of a single continuous line. That makes it awkward (or shall we say challenging), but that's also what makes it unique and interesting. Here's a drawing I made with SimpleEtch: ![]() To change SimplePaint into SimpleEtch isn't especially difficult and I won't explain it in detail here. The basic differences are:
The complete REALbasic project file for SimpleEtch is available here: download it and study the changes I made to see how the variation works.
Next WeekWe finish Monotab. LettersThis week we've got a question from Spain! Manuel writes:
Interesting question. I'm not sure of the answer, since I'm not sure what you are trying to do here. Pascal used to be my favorite language (I never learned C), but it's been a while since I've used it. Unless I'm completely missing something, the statement above is an odd one. The ord() function returns the ASCII value of the character passed. For instance, pass it a "C" and it returns a 67. But in your code you're passing it a boolean value: the "firstValue >= secondValue" phrase is true if firstValue is greater or equal to secondValue, otherwise it is false. That means getValue will be assigned the result of either ord(true) or ord(false), and it's been too long for me to remember what Pascal would do with that. (I suspect it would be either one or zero, since that's what true and false usually evaluate to, but I'm not 100% certain.) At any rate, the REALbasic version of this is similar. Something like this is a literal translation:
However, if you try this, you'll get a "Type Mismatch" error. That's because REALbasic's asc() function is expecting a string and you're passing it a boolean. asc(true) and asc(false) don't work. The way around this, if this is what you are wanting, would be to frame the code within an if-then statement like this:
This is a little more convoluted, of course, but it's doable. If this is a function you expect to use often, I'd suggest you make it into a method. Let's call it ascBool and set it up like this:
If you create the above method, then this code works: getValue = ascBool(firstValue >= secondValue) About the Column REALbasic University is a weekly instructional column on programming with REALbasic and is brought to you by REALbasic Developer, the magazine for REALbasic programmers. Each week we answer select reader questions, and we're always open to ideas for future columns. Send your questions to . (Keep your questions simple and specific. General queries like "How do I write my own web browser?" will be neglected.) Your question won't be answered immediately, but will be answered in a future column. (If you don't want your correspondence published, just be sure to indicate that when you write. Otherwise it's fair game.) About the Author See the REALbasic University Archives
REALbasic University contents ©2001-2004 by Marc Zeedar and REALbasic Developer. All Rights Reserved.
| |||||||||||||||||||||||||||