Click here to Skip to main content
Click here to Skip to main content

Latin Textbook and Look-up Table in C#

, 23 Apr 2010 CPOL
Rate this:
Please Sign up or sign in to vote.
Wheelock's Latin textbook with Cassell's Latin Dictionary, written in C#.

Why Latin?

For years now, I've been making an effort to learn a bit of Latin. Though the learning has been considerably slow, it's given me the opportunity to write a Latin project which has been helpful along the way. In a previous article, Spell Weller, but Grammar's Up to You, I make reference to this Latin project without providing any code, and had some haunting qualms about this remiss. So I determined to dedicate an entire article exclusively to this project and reformat the data in order to make it possible for you to upload it onto this venue and make it publicly available.

The reason why I decided to try to learn Latin in the first place may have been due to a small-c catholic upbringing which could do nothing with my innate atheistic ways, or it may have stemmed from the night I cracked a rib watching Monty Python's Life of Brian.

romani_ite_domum.jpg

Whatever the case may be, you'll find that this application is better than any other Latin project you'll find anywhere. One program that compares is Whittaker's Words, which does much of what this app's Look-up Table does in a DOS window interface. Not as user-friendly as it might otherwise be, but given its download file size and ease to install, it is really quite remarkable!

To start with, I had to personally type the entire first-half (Latin to English from A-Z) of Cassell's Latin/English-English/Latin Dictionary (24000 word entries!) when I first began my Latin studies using Wheelock's Latin textbook about three years ago. Over a two year period, I then wrote the first version of this program in VB in a piece-meal fashion by adding whatever new conjugations or declensions I learned from my hard-cover Latin textbook into the project so that my computer could ease the learning process.

Then, about a year ago, I rewrote the whole thing in about one month using C#. This newer version is much, much better. Since the original VB was so ill-planned (not planned at all!), this C# re-write is considerably faster and more efficient. Since then, I've also typed in the Wheelock's Latin textbook, and have made some modifications to the files which were originally written in a text file format and are now in XML, with formatting information taken from RTF files by way of code from the Writing Your Own RTF Converter CodeProject article which greatly facilitated the task.

The most difficult part in writing the original VB version was teaching the code to figure out what type of word each word entry in the Cassell's dictionary was. Is it a noun? An adjective? Which declension? Verb? Adverb, indeclinable, pronoun, and so on, because that dictionary did not have all the information that a Latin scholar would know in simply looking at an entry. Definitions that started with the word 'to', like 'to run' or 'to write', were assumed to be verbs. Most nouns have only two principal parts, but so do some adjectives. In short, the whole process of getting this working properly took the better part of a frustrating two years, and I had no intention of going through all that again in the C# rewrite, so I used the VB code to copy every word entry from the first hand-typed version, figure out what each word is, then store this information in the word's definition and save it in a separate directory. That done, I was then prepared to take on the C# rewrite, which you have here.

You have to understand that I publish this article, the software, and the copyright protected Cassell's Dictionary & Wheelock's Latin textbook at the risk of receiving angry e-mails from this guy:

this_guy.jpg

who recorded the audio files which are incorporated into this program.

More on that later, but first, let's get this Latin project up and running on your computer.

Installation

The first thing you'll need to do is create a sub-directory 'Latin' on your C: hard-drive. I won't apologize for this bit of hard-coding, but will only suggest that if you're an actual toga-wearing Latinist who already has, for some reason, a "c:\latin\" directory on your hard-drive and think that adding this project there will create a conflict, just change the first line in the form formLatinProject, which you can see here:

namespace Latin_Project
{
    public partial class formLatinProject : Form
    {
        string strWorkingDirectory = "c:\\latin\\";

to whatever suits your fancy, and then follow these instructions with that change taken into consideration.

The five compressed files listed at the top of this article need to be decompressed in the order they appear.

  1. Decompress the first two zip files 0_Latin_AtoL.zip and 1_Latin_MtoZ_plus.zip into the c:\latin\ subdirectory, and you'll have 26 sub-subdirectories subDir_A to subDir_Z with all 24000 XML dictionary entries included.
  2. Then decompress the next file, 2_Latin_Wheelock_V3.zip, in exactly the same way, and you'll have a 27th sub-subdirectory called 'wheelock'.
  3. The fourth file, 3_Latin_Wheelock_Images.zip, needs to be decompressed into the c:\latin\wheelock\ sub-directory. And now, you've got all the files you'll need to run the program.
  4. For you non-coding newbies who stumbled in here without a clue what you were getting into, you'll have to download and install C# 2010 from Microsoft Express Download. This is where you might want to call the grand-kids for some help.
  5. Assuming you now have all that on your computer, you can go ahead and download the source code, compressed in the file latin_src_v8.zip, decompress it wherever you like, then load it inside your C# IDE (integrated-development environment), and press F5.

Optional: You can download those audio files from Wheelock's website for free. Decompress them directly into the c:\latin\ directory, and the computer will detect them and figure out what to do. At this point your hard-drive should look something like this (minus those two mega files):

Directory_View.PNG

Now you're all set!

No, not quite. The program is good to go like that, but you'll be missing out on the main feature: the 'Look-Up Table' or LUT. I'll tell you more about that later; for now, when you run the program, it'll tell you that it doesn't have the files for the LUT, and you'll have to let it do some work on its own before you can have this very handy feature. The reason for this extra work is that the files which the program uses when it's done are close to 1GB in size, and are therefore too big to include with this article. But no worries, when it prompts you and asks if you want to create the LUT files, just say 'no' for now, and it'll ask you again the next time. I'll write more about the LUT down below.

Latina est Gaudium

Yes, Latin is a joy! But first, you'll have to pick up on the language because this isn't pig-latin we're talkin' you know! The language is old, and for all intents and purposes, currently considered dead, but that's no reason to kick it when it's down. Let me tell you a bit about Latin and why this program is more than just an electronic text-book. Nouns all have a gender, including a third gender called 'neuter', which means 'neither' in Latin (bet you didn't know that!). Then each noun and adjective has six cases and two numbers (singular and plural). What's more is adjectives have three different types of ways to be used positive, comparative, and superlative; all adjectives have three genders, all have two numbers, and all have six cases:

latin_101.PNG

3 (genders) x 3 (types) x 2 (numbers) x 6 cases = 108 different ways to spell the same adjective.

Every adjective.

And verbs?!? Between passive and active, plural and singular, you have the persons first, second, and third. Present, past, and future, subjunctive, participles, infinitives... there's a lot! And they're all very similar, but different one from the next. And that's if you figure out if the verb is first conjugation, second, or third! Or maybe, it's a third IO. Then there's this scary thing called the 'passive periphrastic'.

In short, what I'm saying is, if you're trying to learn Latin with nothing but a textbook, you're going to run into some trouble. And that's where this program comes in.

Opifex: Skilled worker

When you first launch the program, you'll be looking at the front cover to Wheelock's Latin. The gray underlined-type print beneath the image are all links to other parts of the book. The textbook part of the program works in a way similar to HTML and the internet, except that it's a network of files that are all contained in a single directory on your hard-drive which you can navigate through. This is done using a class called xmlRecord, in combination with classGraphics, which I talked about in GCIDE: A Complete English Language Dictionary.

classXmlLatin is similar, but does not have the links between files which join the network of files that make up the textbook. This is because the dictionary files which it handles do not require any network, and are loaded exclusively using references from the Look-Up Table. Remember the LUT mentioned earlier? This is where the Latin actually happens. classLatin relies on classXmlLatin to retrieve the dictionary entries which it uses to create the look-up table.

When you finally get around to letting the program build you you LUT, you'll find that the program will be very MIPs needy while in the process of building this database. It will keep your computer particularly busy for nearly 24 hours! You can stop and restart it any time you like at no extra cost, and you may want to just let it work overnight. That seems like a long time, but if you're downloading this program because you need (or want) to learn Latin, then you won't want to go without it.

classLatin uses an opifex, or skilled worker, to do all the work. The program only needs to instantiate one classLatin; this class then creates itself a resource handler as well as that famous opifex on which it relies. The resource handler manages classWordInfo. These word-info objects hold all the information from the dictionary entries which the opifex requires to conjugate or decline, whatever the case may be. classWordInfo also has two combo boxes which can be put on the screen if need be. Any number of these can be requested from the resource-manager, and whenever the user makes a selection change on either combo boxes, classWordInfo will pass itself over to the opifex to deliver the Latin.

The actual code for classOpifex is not altogether very complicated because all the work is divided into a multitude of functions that are named with their specific tasks in mind. For most conjugation/declension functions, the opifex receives as parameter an instance of a classWordInfo. Since each function is written for one exclusive purpose, that's all any of these functions need. In the original Visual Basic version of this program, I made the mistake of trying to have one function do everything, and discovered that that created new problems with every new chapter of the textbook. This C# version doesn't have any one function with an "I can do it all" attitude, but rather relies on a small army of functions that each handle a different Latin conjugation or declension.

Here's an example ....

public classLatin_C.udtSolution conjugate_Present_Active_Indicative(classWordInfo word)
{
    if (verbIsDeponent(word))
        return conjugate_Present_Active_Indicative_Deponent(word);

    classLatin_C.udtSolution udrRetVal = new classLatin_C.udtSolution();
    string[,] strSolution = new string[2, 3];
    udrRetVal.strSolution = strSolution;
    udrRetVal.typeSolution = 
       getTypeSolution(classLatin_C.enuVerbTenses.Present_Active_Indicative );
    // 1st, 2nd, 3rd, 4th, 3rd-IO 
    string[, ,] strEndings = {{{"ō", "ās","at"}, {"āmus","ātis","ant"}},
                               {{"eō", "ēs","et"}, {"ēmus","ētis","ent"}},
                               {{"ō", "is","it"}, {"imus","itis","unt"}},
                               {{"iō", "īs","it"}, {"īmus","ītis","iunt"}},
                               {{"iō", "is","it"}, {"imus","itis","iunt"}}};

    string strBase = word.strWords[1];
    strBase = strBase.Substring(0, strBase.Length - "are".Length);
    classLatin_C.enuVerbConjugations verbConjugation = 
                 getVerbConjugationFromWordType(word.wordType);

    for (classLatin_C.enuNumber numberCounter = classLatin_C.enuNumber.Singular;
                                numberCounter <= classLatin_C.enuNumber.Plural;
                                numberCounter++)
        for (classLatin_C.enuPerson personCounter = classLatin_C.enuPerson.First;
                                    personCounter <= classLatin_C.enuPerson.Third;
                                    personCounter++)
            udrRetVal.strSolution[(int)numberCounter, (int)personCounter] = 
               shortenLongVowels(strBase + strEndings[(int)verbConjugation, 
               (int)numberCounter, (int)personCounter]);
    udrRetVal.strRowNames = getRowNamesOfConjugation();
    udrRetVal.strColumnNames = getColumnNamesOfDeclension();

    return udrRetVal;
}

In this example, the opifex is being requested to conjugate the word it is provided in the Present Active Indicative. It will first ask "is this a deponent verb?", and if so, branch off to the opifex function which will take care of it if it is. Otherwise, it creates an instance of a structure called udtSolution and translate the verb tense into a typeSolution (two enumerated types). Then, because the Latin rule for this case is to chop off the last letters of the verb's second participle (strWord[1] in classWordInfo) before adding the personal endings, it generates a variable called strBase and proceeds to add the appropriate endings, fetches the row-names and column-names for this type of solution, and wires it all back to the calling function.

If you take a closer look at these functions, you'll find that they all pretty much do the same thing, with only minor variations for their specific tasks. Because there are so many ways to conjugate and decline nouns, adjectives, and verbs in Latin, it was just easier to do things this way than to worry about saving code-space by consolidating them all into one, or even only just a few functions. This design makes debugging much easier. This way, if next year I find that I've been declining third declension feminine superlative adjectives incorrectly, there's only one function to fix, and it won't affect anything else when I debug it.

Bada-bing, bada-boom.

In combo two

Whenever a dictionary entry appears on the screen, you'll find it comes with a few extra features besides the definition. Looking at the two combo-boxes above the word's entry, you'll see that the one on the left tells you what kind of word you're looking at, e.g., magnus, -a, -um: first declension adjective. Listed below this, you would see the three genders for each of the three ways this adjective can be declined: positive, comparative, and superlative. You can choose any one of these entries and get an output. Selecting any specific gender will give you the two numbers and 6 case spellings for this adjective for the gender/type you've chosen. Clicking on the Comparative heading, somewhere near the middle of the list, will produce another form with all 3 genders x 2 numbers x 6 cases (all the comparative spellings of magnus!), and if you're still not sure what you're looking for, the top most heading with the word's type-info I mentioned earlier will spit out all 108 ways this particular adjective can be spelled onto the screen in one clean sweep.

Verbs and nouns are similar, but with tenses, passive and active, or in the case of nouns, just the same as adjectives, but only for the one gender. Just play around with it, and you'll see what I mean.

And in the right corner!

The other combo-box is something I call Latin logic. This is loaded with links to the Wheelock's Latin textbook's pages that are specifically related for the particular word you're looking at. You might be curious to know what a third conjugation-IO verb is when you popup a dictionary form with the definition for the word capio, -ere, and scanning over the list of entries there, you'll find exactly what you're looking for. Then, when you select an entry from the Latin Logic combobox, a whole new instance of your Latin textbook pops up on exactly the right page, chapter 10, sub-chapter "IO verbs", for you to read at your delight.

There's an LUT to do

You really should have your program build you a Look-up Table because without it, you're missing out on a lut!(sic). Let me explain. If you haven't been able to actually see a dictionary entry and tell the opifex to get to work for you, that's because you haven't yet got your look-up table on-line. Once you do, it's a wiz. What the look-up table does is very similar to what the GCIDE article mentioned above does: it helps you find what you're looking for. It tells you what a word is, how many ways that particular spelling can be derived, and where to find the root of the word you're looking at. To do this, the program goes over every entry in the Latin/English dictionary, it loads each word-entry one at a time, generates every possible spelling it knows for all of these words, then stores them into a database along with the means by which each spelling was obtained. Then, when the user asks it what the word capiunt is, for example, it scans the database and tells you that capiunt is the word you would have found in your pocket Latin dictionary listed under capio, -ere, cepi, captum had you picked it up off the shelf; however, the LUT will also tell you that it's conjugated in the Present Active Indicative, third person, plural, which you would otherwise have to figure out for yourself.

LUT__hard__at_work.PNG

The image above shows you the textbook page 'On a Temperamental Friend' at the top. Clicking on the word difficilis which appears in the first Latin sentence shows you the difficilis form which lists all the possible ways this particular spelling can be derived, and selecting the first one pops up the next form in front of it (dictionary entry with its two combo-boxes - F1) or the blue form in front of that one (positive masculine declension - F3). You'll notice that this word also shows the "audio" button, which when pressed, audibly tells you how the root of this word is pronounced (that is, if you've downloaded the audio files). Notice the pretty blue? Feminine nouns and adjectives are displayed in pink, and neuters in gray.

Latin is particular because had you been looking for the word "ceperant" in your pocket dictionary, you'd probably not think to look under "capio" and may or may not find it. But with this LUT, all that is done for you! Unfortunately, so is most of the thinking, which makes it very easy to rely entirely on the program and not do any actual Latin-learning at all, as I have so happily discovered.

The LUT first gives you a list of ways the word can be derived. From there, you can ask it to give you the dictionary form of the entry you've selected, or to just put the solution to the screen straightaway. You can do either of these by clicking F1 for one or F3 for the other. Alternately, you can click the left half of the form or the right, and a pop-up helpful text tells you which you're about to select.

The program's mode selection allows you to choose between workbook, textbook, and vocabulary. textbook is the default start-up value, and this is where you click on words, links, or images. Clicking on a Latin word will cause the program to search the LUT for the word you clicked, making reading Latin just a little bit less daunting for any newbie. If you happen to click a word it doesn't find in the LUT, the program won't scowl or argue, but rather it'll kick back and wait for your next request. This way, you can click an English-word and need not worry about confusing it, though this would be a good place to include the GCIDE project if you already have it up and running on your computer. The PictureBox's mouse-click event in the form classGraphicOutputPanel below shows you where:

void pic_MouseClick(object sender, MouseEventArgs e)
{   
    if (wordUnderMouse != null) 
    {
        switch (wordUnderMouse.eWordImageType)
        {
            case enuWordImageType.link:
                loadFile(wordUnderMouse.strAddress);
                cutNavigation();
                addNavigationPage();
                break;

            case enuWordImageType.solution:
                frmPopUp.Owner = mainForm;
                frmPopUp.showText(wordUnderMouse.strAddress);
                break;

            case enuWordImageType.image:
                Form frmPic = new Form();
                PictureBox pic = new PictureBox();
                frmPic.Controls.Add(pic);
                pic.SizeMode = PictureBoxSizeMode.AutoSize;
                pic.Image = wordUnderMouse.bmp;
                frmPic.Size = new Size(pic.Width + 15, pic.Height + 35);
                frmPic.Text = wordUnderMouse.strText;
                pic.Location = new Point(0, 0);
                frmPic.KeyDown += new KeyEventHandler(cLibLatin.frm_KeyDown);
                frmPic.Show();
                break;

            case enuWordImageType.plain:
                cLibLatin.searchLUT(cLib.removeNonAlpha(wordUnderMouse.strText));
                // add GCIDE project's searchLUT here
                break;
        }
    }
}

The textbook is nothing more than a textbox with one extra feature; you guessed it, it is a Latin-LUT friendly zone. Just place the cursor on the word you've typed and want to know more about, and then press F3.

Presenting the lovely Look-Up Table Panel

The left of the main form includes a hidden panel for your LUT controls.

LUT_panel_v2.PNG

Aside from giving credit to this program's source material, it offers you the option of using the Latin-LUT in any other program by enabling a timer which tests the contents of Microsoft's clipboard for text. Whenever it finds text there that it hasn't seen before, it'll clip off the first word and plug it into the look-up table. This is convenient when you're reading screen-plays of Supernatural or researching the Vatican's Documenta Lingua Latina for a good exorcism, or if you're just curious to know if that familiar brand-name you've trusted all these years is actually a Latin epithet in disguise. To use it, when you've checked the checkbox on the LUT-panel, you need only select the text you want to run through the LUT, i.e., on your web-browser, and copy it into the Operating-System's clipboard by highlighting it and then pressing the key-combination Ctrl-C.

Search panel

search_pnl_v2.PNG

Latin-wise, sometimes you need to search your textbook for something you may have missed. And that's what this does. Selecting the Heading checkbox searches every chapter title and sub-chapter title from your Wheelock's textbook for the words you're looking for. Glossary searches only the glossary words and their English definitions. This is convenient when you're trying to translate from English to Latin. If you want to know the words for "girl" in Latin, you can search the glossary for it and the results will give you the glossary word puella, -ae which has the word girl in its definition. From there, you can click on the word puella and have the look-up table spill out the 12-part declension in pretty pink! Not checking either of these check-boxes will search everything in the textbook apart from the glossary entries, from every smallest footnote to the longest dreariest chapter, ahem, I mean "to the most erudite chapter".

Code-wise, you may find the panel's buttons interesting. The background image is stored in the program's resources and then copied onto the panel at run-time. Then, each button/checkbox is placed onto the panel, and for each one, a bitmap is created from the same source image and placed strategically onto that object so as to complete the picture. This makes for a nice graphic effect.

As for the actual code which does the searching, it's got some similarities to the LUT in that it relies on binary-stream files with records of identical size which can be retrieved and saved in a random access fashion. These records each contain a word that can be searched, along with the file in which it can be found and three integer variables used to create a binary-tree/linked list that make up the database. Whereas the LUT uses two separate files, one for the binary tree and the other to hold all the linked list entries which each leaf in the binary tree points to, the search engine has all the information on a single file. Or three, rather, since there are three different types of searches: normal, glossary, and heading, which each "require" their own file. I say "require" in quotations, but if the three different trees had their root nodes at records 1, 2, and 3, respectively, then all three search engines could be stored in the same file. You would only need to know how many trees you want in the file and reserve those three entries for the root-nodes of each tree. Putting that idea aside, this program uses three different files for the three different search modes. There's only one actual search-engine, and since the getRecord() and putRecord() functions get a Mode parameter, they can open and close the appropriate file at every execution. Mode is an enumerated type, and the three file names are stored in an array with the enumerated type's integer values in mind so that getting the right file is as easy as indexing it with the mode type.

classSearchRec_FileTreeElement getRecord(long index, enuTypesSearches Mode)
{            
    FileStream fs;
    if (index < 0)
        return null;
    if (System.IO.File.Exists(strSearchBinFilenameAndDirectory[(int)Mode]))
    {
        fs = new FileStream(strSearchBinFilenameAndDirectory[(int)Mode], FileMode.Open);
        fs.Position = getBinPosition(index);

        BinaryFormatter formatter = new BinaryFormatter();
        string strFilename = (string)formatter.Deserialize(fs);
        string strWord = (string)formatter.Deserialize(fs);

        classSearchRec_FileTreeElement cRetVal = 
           new classSearchRec_FileTreeElement(strWord, strFilename);

        cRetVal.lngLeft = (long)formatter.Deserialize(fs);
        cRetVal.lngRight = (long)formatter.Deserialize(fs);
        cRetVal.lngNext = (long)formatter.Deserialize(fs);

        cRetVal.intSizeLinkedList = (int)formatter.Deserialize(fs);
        fs.Close();
        return cRetVal;
    }
    return null;
}

You can see in the second if statement above where the IO system tests the existence of the search-engine's file which is stored in the strSearchBinFilenameAndDirectory[] array indexed by the integer value of the input parameter Mode.

public class classSearchRec_FileTreeElement
{
    public long lngLeft;
    public long lngRight;
    public long lngNext;
    public string strWord;
    public string strFilename;
    public int intSizeLinkedList;

    public classSearchRec_FileTreeElement(string Word, string filename)
    {
        lngLeft = -1;
        lngRight = -1;
        lngNext = -1;
        intSizeLinkedList = 1;
        strWord = Word;
        strFilename = filename;
    }
}

A more interesting part of the search engine may be the way it does the boolean searches. Whenever the user types more than one word into the search engine, the program needs to find the results and put them to the screen. To understand how this works, you first have to know that when the search-engine's database was created, every file was scanned, every word in every file was entered into the appropriate databases, and no doubles of any one word from any one file made their way into the database. So even if the word "the" appears twelve times in any one particular file, it is entered into the database as having been found in that file "at least once" and no more. Also, whenever multiple files have the same word, the binary tree has to branch out at that particular leaf into a linked list (all in the same file, using the same record type which has all three integer indices for the binary tree links intLeft and intRight as well as the linked-list intNext). Whenever inserting new finds into the binary-tree leaf's linked-list, the leaf of the tree (as opposed to a node in the linked list it points to) keeps track of the number of entries in the linked list. This amounts to keeping track of how many files have this particular word in them without having to count the number of entries in the linked list. Since the search engine need only be built once and is then only referenced and is never edited, storing this value in the first element of the list makes it readily available.

And so, when the search engine finds itself having to cross reference two or more lists of filenames to see if any of them contain both of the words you're comparing, it does this by first generating a binary tree in RAM memory of the shortest list, then runs every entry from the next shortest list into this binary tree. Filenames contained in the second list which are also found in the first list's bin-tree of filenames then go into another bin-tree, which is subsequently used for the third word's file list. The code keeps an array of two pointers to the roots of these RAM trees, and alternates between the two, scanning bigger and bigger lists through smaller and smaller trees, until it eventually scans an entire list without finding a single filename in the previous tree, at which point, it quits, or it has scanned all the lists and now holds the names of all the files which contain every search word the user is looking for in the last created RAM bin-tree.

public class classSearchRec_RAMTReeElement
{
    public classSearchRec_RAMTReeElement Left;
    public classSearchRec_RAMTReeElement Right;
    public classSearchRec_RAMTReeElement Next;
    public string strWord;
    public string strFilename;
    public int intSizeLinkedList;

    public classSearchRec_RAMTReeElement(string Word, string filename)
    {
        Left = null ;
        Right = null ;
        Next = null;
        intSizeLinkedList = 1;
        strWord = Word;
        strFilename = filename;
    }
}

All this is done in classSearchEngine's

public string[] searchWord(string strSearchWords, enuTypesSearches eMode)

Pop-up form

The button seen in the image below at the top right of the form with the ^ on it produces a new pop-up form loaded with the current page and has all the features of the app's main form. Its "home" navigation button will return you to the file it was loaded with, and so if you get lost stomping the grounds, you can always find your way.

Panels and modes

side_panels.PNG

In the image above, you can see the three different side-panels which appear on the screen. I call them side-panels because the tabs that stick out along the left edge of the form can be clicked on to bring them to the front, and that's what they look like when all three of them have been brought forward in that way. When you click on them again, all you see are the words on the left edge sticking out the side, thus the name. I've already talked about the search panel and the LUT vanity-panel, but you haven't heard about the other one labeled Mode. No mystery here, and it may not need be said, but this is where you select what mode you want the app to be in.

Vocabulary mode

From the mode-panel, you can select either of the three modes: Textbook, Workbook, and Vocabulary mode. Clicking on the Vocabulary radio-button brings you here:

vocabulary_mode.PNG

This is the Vocabulary mode. You can review your Latin-vocabulary using flash-cards on your computer. classVocabulary is the groupbox you see in the above image with the word Vocabulary written on it. The picture grows and shrinks to keep up appearances, but more importantly, the combo-box on the right has a list of all of Wheelock's Vocabulary Chapter files (forty chapters), and you can select or unselect from which chapters you want your flashcards to be chosen at random the next time you review your Latin vocabulary. Whenever you select a chapter, all the words contained in that chapter are added to a running list of words to review. Then, when you click the button Review Words beneath the combo-box, a number of new words are randomly picked out of your selected review chapters and added to your current list of vocabulary words before the flash card appears with the first word for you to study.

flash-card_front.PNG

The image above shows you the front of the flash card. The word appears in Latin; in this case, the Latin verb cogito with its four principal parts. Moving the mouse around, you'll find that the three words near the top, "Delete", "Next", and "Quit", are click-activated and work like buttons. But first, you'll want to flip the card over. To do this, you grab the image anywhere that's not a button and move the mouse vertically while holding down the mouse-button. This will animate a flipping motion, which allows you to display either side of the flash-card depending on the position of the mouse.

The flipping of the card illusion is a simple trick. There are two picture boxes in stretch-size mode, fill-docked to the form, one on top of the other. The program keeps track of which side you're looking at with one variable, and remembers which side you grabbed with another, so that when the mouse is moved, the image is contracted and expanded depending on where it was grabbed, above or below the middle of the screen, and where the mouse cursor is at the moment, also relative to the middle of the screen, then decides which image belongs in front of the other. This is all done in the MouseMove event of classFlashCard (in the classVocabulary.cs file).

Notice the boolean global variable bolIgnoreMouseMove. Because all the action happens inside the mouse-move event handler which will move the form, affect the image beneath the mouse, and cause more events to handle, those events must be ignored until it's done doing what it's got to do, or they will cascade into disaster. This boolean is tested before it is set at the start of the event-handler, and then it is reset before exiting, allowing the handler to cause more events in the process of updating the form, certain that those events will be ignored until it's done.

void pic_MouseMove(object sender, MouseEventArgs e)
{
    if (bolIgnoreMouseMove)
        return;
    bolIgnoreMouseMove = true;

    if (bolGrab)
    {
        Point ptMousePosition = new Point(MousePosition.X, MousePosition.Y);
        if (distanceBetweenTwoPoints(ptMousePosition, ptOldMousePosition) > 5)
        {
            ptOldMousePosition = ptMousePosition;

            int intNewHeight = 
                Latin_Project.Properties.Resources.flashcard_source_back.Height;
            int intNewTop = intMidScreen;
            int intGrabDist_Abs = Math.Abs(intGrabDistFromCenter);
            int intMidHeight = Height / 2;
            int intDistanceFromCenter = Math.Abs(e.Y - intMidHeight);

            bool bolSetToNewValues = false;

            if (intGrabDistFromCenter < 0)
            { // grabbed it above mid-point
                if (e.Y > intMidHeight - intGrabDist_Abs)
                { // in the process of flipping or completed action
                    if (e.Y > intMidHeight)
                    { // seeing the side not-grabbed
                        eVisibleSide = otherSide(eGrabSide);

                        if (e.Y < intMidHeight + intGrabDist_Abs)
                        {
                          // fraction of size (still flipping)
                          intNewHeight = (int)(((double)intDistanceFromCenter / 
                           (double)intGrabDist_Abs) * 
                           Latin_Project.Properties.Resources.flashcard_source_back.Height);
                        }
                        bolSetToNewValues = true;
                    }
                    else
                    { // still seeing the grabbed-side
                        eVisibleSide = eGrabSide;

                        if (e.Y > intMidHeight - intGrabDist_Abs)
                        {
                          // fraction of size (still flipping)
                          intNewHeight = (int)(((double)intDistanceFromCenter / 
                           (double)intGrabDist_Abs) * 
                           Latin_Project.Properties.Resources.flashcard_source_back.Height);
                        }
                        bolSetToNewValues = true;
                    }
                }
            }
            else
            {
                // grabbed below mid-point -> (intGrabDistFromCenter > 0)
                if (e.Y < intMidHeight)
                { // seeing the side not-grabbed
                    eVisibleSide = otherSide(eGrabSide);

                    if (e.Y > intMidHeight - intGrabDist_Abs)
                    {
                      // fraction of size (still flipping)
                      intNewHeight = (int)(((double)intDistanceFromCenter / 
                       (double)intGrabDist_Abs) * 
                       Latin_Project.Properties.Resources.flashcard_source_back.Height);
                    }
                    bolSetToNewValues = true;
                }
                else
                {
                   // still seeing the grabbed-side
                    eVisibleSide = eGrabSide;

                    if (e.Y < intMidHeight + intGrabDist_Abs)
                    {
                      // fraction of size (still flipping)
                      intNewHeight = (int)(((double)intDistanceFromCenter / 
                       (double)intGrabDist_Abs) * 
                       Latin_Project.Properties.Resources.flashcard_source_back.Height);
                    }
                    bolSetToNewValues = true;
                }
            }
            if (bolSetToNewValues)
            {
                Height = intNewHeight;
                Top = intMidScreen - intNewHeight / 2;
            }

            pic[(int)eVisibleSide].BringToFront();
        }
    }
    else
    {
        cWordUnderMouse = mainForm.cLibGrText.getWordUnderMouse(
           new Point(e.X, e.Y), uWords[(int)eVisibleSide].cWord);

        if (cWordUnderMouse != null && 
                  cWordUnderMouse.eWordImageType == enuWordImageType.link)
            Cursor = Cursors.Hand;
        else
            Cursor = curNormal;
    }

    bolIgnoreMouseMove = false;
}

flash-card_back.PNG

cogito ergo sum...

The numbers on the top left tell you how many flash cards are still left in this review session and the number of flashcards still left for the next. Clicking on the "Delete" option discards the one you're looking at so that it won't automatically be included in the bunch the next time you review your Latin vocabulary. Alternately, clicking the "Next" option replaces the card you're holding back in the review pile, and another one is randomly picked from the lot.

Salve!

Latin may not be the oldest language around, but it has spawned all the Romance languages spoken today. It can be found throughout history from "Iacta alea est" to "Sic semper Tyrannis" as well as literature's great heroes like "Captain Nemo". Latin is everywhere, and if you don't want to actually learn the language, or you don't have the time, energy, or will, you can still use this program to make simple translations.

And who knows when next you'll need to make a quick demonic exorcism!

Updates

  • April 16, 2010: Updated source code, fixed a few minor bugs, moved the side panels to the bottom, and added a new 'popup' form button.
  • April 20, 2010: Moved the side panels, and added 'Vocabulary words' and flash-cards.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Christ Kennedy
CEO unemployable
Canada Canada
Christ Kennedy, published his fourth novel "Cleats of the Counter Revolution" in the summer of 2010. He grew up in the suburbs of Montreal and is a bilingual Quebecois with a bachelor’s degree in computer engineering from McGill University and is currently walking across ontario plotting a new novel, far away from any computer.

Comments and Discussions

 
QuestionGreat writeup... what about Mr. Wheelock? PinmemberJksoft28-Aug-12 5:59 
GeneralMy vote of 5 PinmemberVestitius1-Aug-12 1:50 
QuestionWow! PinmemberBill Prada26-Jan-12 6:21 
GeneralDirectX Pinmemberjimmygyuma27-Apr-10 9:49 
GeneralRe: DirectX PinmemberChrist Kennedy27-Apr-10 17:16 
Generalsounds ql PinmemberSeishin#22-Apr-10 2:29 
GeneralRe: sounds ql PinmemberChrist Kennedy22-Apr-10 2:48 
GeneralRe: sounds ql PinmemberSeishin#22-Apr-10 2:56 
GeneralRe: sounds ql PinmemberChrist Kennedy22-Apr-10 4:06 
Generalmissing Source code? resolved PinmemberChrist Kennedy15-Apr-10 1:36 
GeneralMy vote of 1 Pinmemberg0got214-Apr-10 20:43 
GeneralRe: My vote of 1 PinmemberJohnny J.14-Apr-10 21:55 
GeneralRe: My vote of 1 PinmemberChrist Kennedy14-Apr-10 23:13 
GeneralRe: My vote of 1 Pinmemberjp2code25-May-10 6:25 
GeneralRe: My vote of 1 PinmemberChrist Kennedy25-May-10 7:01 
GeneralDear Christ! Pinmembersam.hill14-Apr-10 19:30 
GeneralRe: Dear Christ! PinmemberJohnny J.14-Apr-10 21:57 
GeneralRe: Dear Christ! [modified] PinmemberChrist Kennedy14-Apr-10 23:11 
GeneralRe: Dear Christ! Pinmembersam.hill15-Apr-10 5:50 
I meant "weird" in the best sense of the word.
Otherwise, I would not have scored it a "5".
The weirdness has nothing to do with Latin; I have studied Latin myself and am reasonably facile in the language. I was actually referring to the presentation style.
Note to Johnny J.: assumptions are dangerous.
GeneralRe: Dear Christ! PinmemberChrist Kennedy15-Apr-10 6:08 
GeneralRe: Dear Christ! PinmemberJohnny J.16-Apr-10 0:02 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web04 | 2.8.141216.1 | Last Updated 23 Apr 2010
Article Copyright 2010 by Christ Kennedy
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid