KTouch lession maker

Some of you might have landed on this article searching for the book instead, here it is 🙂

I have been trying to improve my typing by learning Dvorak and so far I’ve been to be able to type more accurately (I’m not talking about speed here :p). I have been using the Grass Soft touch typing program to train on Windows, it is pretty useful except for the limited word list. They claimed that they have included 500 most used word in English. That just doesn’t seem enough to me!

Besides, I have also started to use Linux and I needed a program that will keep me busy on the new platform. Of course in the open source world there’s always a lot of option too choose from, most of them are free too. I have chosen KTouch as it seems to be under active development and also endorsed by KDE.

KTouch with my customized lesson

So far, it’s a great program! It automatically detects your keyboard layout and select the appropriate starting lesson. It have two lesson for Dvorak: ABCD and computer generated. Despite the name, the computer generated lesson does not change as you take it because it’s just a file pre-rendered by the developer. I hit the same obstacle I did with the previous touch typing program: no variety.

Fortunately KTouch allows you to load external lesson files (they are just XML files) and text file; but due to poor application design, the text file won’t let you type the whole file but only the first few sentences. Which means if you want to practice with a long text, you’ll have to split them manually into multiple files.

<?xml version="1.0" encoding="utf-8"?><KTouchLecture>
<Title>Puzo, Mario - The Godfather.txt</Title>
<Comment>This is a lession created from Puzo, Mario - The Godfather.txt</Comment>
<FontSuggestions>Courier 10 Pitch</FontSuggestions>
<Levels>
<Level>
<LevelComment>level 1</LevelComment>
<NewCharacters>a lot :)</NewCharacters>
<Line>Chapter 1</Line>
<Line>Amerigo Bonasera sat in New York Criminal Court Number 3 and waited for</Line>
<Line>justice; vengeance on the men who had so cruelly hurt his daughter, who had</Line>
<Line>tried to dishonor her.</Line>
<Line>The judge, a formidably heavy-featured man, rolled up the sleeves of his black</Line>
<Line>robe as if to physically chastise the two young men standing before the bench.</Line>
</Level>
</Levels></KTouchLecture>

Structure of XML lesson files

So I made a program with C# to make KTouch lesson out of text files. Thanks to C#’s ability to process strings and XML, this has been a fairly easy task, here’s the code:

            const int LinePerLevel = 6;
            const int CharactersPerLine = 80;
            FileInfo Info = new FileInfo(Filename);
            string[] RawData = File.ReadAllLines(Filename, Encoding.Default);
            XmlTextWriter Writer = new XmlTextWriter(Filename + ".xml", Encoding.UTF8);
            Writer.WriteStartDocument();
            Writer.WriteStartElement("KTouchLecture");
            Writer.WriteRaw("rn");
            {
                Writer.WriteElementString("Title", Info.Name);
                Writer.WriteRaw("rn");
                Writer.WriteElementString("Comment", "This is a lession created from " + Info.Name);
                Writer.WriteRaw("rn");
                Writer.WriteElementString("FontSuggestions", "Courier 10 Pitch");
                Writer.WriteRaw("rn");

                Writer.WriteStartElement("Levels");
                Writer.WriteRaw("rn");
                {
                    int LevelCount = 1;
                    int LineCount = 0;
                    string[] LevelLines = new string[LinePerLevel];
                    string Buffer = "";
                    for (int i = 0; i < RawData.Length; i++)
                    {
                        string TrimmedLine = RawData[i].Trim();
                        if (string.IsNullOrEmpty(TrimmedLine))
                            continue;

                        TrimmedLine = TrimmedLine.Replace('“', '"');
                        TrimmedLine = TrimmedLine.Replace('”', '"');
                        TrimmedLine = TrimmedLine.Replace('’', ''');
                        TrimmedLine = TrimmedLine.Replace('‘', ''');
                        TrimmedLine = TrimmedLine.Replace("t", "");

                        if (Buffer.Length > 0 && (Buffer[Buffer.Length - 1] == '.' || Buffer[Buffer.Length - 1] == '?' || Buffer[Buffer.Length - 1] == '!'))
                            Buffer = Buffer + " " + TrimmedLine;
                        else
                            Buffer = Buffer + TrimmedLine;

                        while (LineCount < LinePerLevel && Buffer.Length > CharactersPerLine)
                        {
                            int CutOffSpacePos = Buffer.LastIndexOf(" ", CharactersPerLine - 1);
                            LevelLines[LineCount++] = Buffer.Substring(0, CutOffSpacePos);
                            Buffer = Buffer.Substring(CutOffSpacePos + 1);
                        }

                        if (Buffer.Length < = CharactersPerLine && LineCount < LinePerLevel)
                        {
                            LevelLines[LineCount++] = Buffer;
                            Buffer = "";
                        }

                        if (LineCount >= LinePerLevel)
                        {
                            Writer.WriteStartElement("Level");
                            Writer.WriteRaw("rn");
                            {
                                Writer.WriteElementString("LevelComment", "level " + LevelCount.ToString());
                                Writer.WriteRaw("rn");
                                Writer.WriteElementString("NewCharacters", "a lot :)");
                                Writer.WriteRaw("rn");
                                for (int j = 0; j < LevelLines.Length; j++)
                                {
                                    Writer.WriteElementString("Line", LevelLines[j]);
                                    Writer.WriteRaw("rn");
                                }
                            }
                            Writer.WriteEndElement();
                            LevelCount++;
                            LineCount = 0;
                        }
                    }

                    while (!string.IsNullOrEmpty(Buffer))
                    {
                        while (LineCount < LinePerLevel && Buffer.Length > CharactersPerLine)
                        {
                            int CutOffSpacePos = Buffer.LastIndexOf(" ", CharactersPerLine - 1);
                            LevelLines[LineCount++] = Buffer.Substring(0, CutOffSpacePos);
                            Buffer = Buffer.Substring(CutOffSpacePos + 1);
                        }

                        if (Buffer.Length < = CharactersPerLine && LineCount < LinePerLevel)
                        {
                            LevelLines[LineCount++] = Buffer;
                            Buffer = "";
                        }

                        if (LineCount >= LinePerLevel)
                        {
                            Writer.WriteStartElement("Level");
                            Writer.WriteRaw("rn");
                            {
                                Writer.WriteElementString("LevelComment", "level " + LevelCount.ToString());
                                Writer.WriteRaw("rn");
                                Writer.WriteElementString("NewCharacters", "a lot :)");
                                Writer.WriteRaw("rn");
                                for (int j = 0; j < LevelLines.Length; j++)
                                {
                                    Writer.WriteElementString("Line", LevelLines[j]);
                                    Writer.WriteRaw("rn");
                                }
                            }
                            Writer.WriteEndElement();
                            LevelCount++;
                            LineCount = 0;
                        }
                    }
                }
                Writer.WriteEndElement();
            }
            Writer.WriteEndElement();
            Writer.WriteEndDocument();
            Writer.Close();

It does:

  • Convert Unicode punctuations to their ordinary counterpart. For example “ and ” will be converted to “
  • Trim and combine different lines from different paragraphs to they’ll fit into the line format of KTouch. KTouch does not work well with long lines (you can’t see much toward the end of the line) so by default the line is trimmed at 80 characters

For a text source to practice, I decided to drop by project Gutenberg (FYI they digitize out-of-copyright books). They offer a range of about 3000 books in a dozen of languages to choose from. The books are mostly in plain text format, which is just perfect for this purpose.

The first novel I have chosen to type is Godfather by Mario Puzo 🙂 If you want to have a ride too then here’s the lesson maker’s source and here’s the lesson file for Godfather (note that due to bugs in KTouch, some characters toward the end of each lesson will disappear). The lesson file has over 2000 levels available for practicing.