qtHiero: Open-source Egyptian hieroglyph editor

I’m just starting out with Qt4 and C++ and came up with this semi-useful little tool for marking up Egyptian hieroglyphs in MdC.

So far the only annoying Qt-quirk I’ve found is the lack of support for non-BMP unicode characters in the QChar type. Turns out you need to use a QString with two QChars, which is exactly the situation which QChar is supposed to solve (by being larger than 8 bits so that there is a 1-1 correspondence between written characters and QChars in a string).

The unfortunate hack I had to put in for fetching a hieroglyph from a codepoint looked like this:

/**
 * Return a QString from a unicode code-point
 **/
QString MainWindow :: unicode2qstr(uint32_t character) {
	if(0x10000 > character) {
		/* BMP character. */
		return QString(QChar(character));
	} else if (0x10000 <= character) {
		/* Non-BMP character, return surrogate pair */
		unsigned int code;
		QChar glyph[2];
		code = (character - 0x10000);
		glyph[0] = QChar(0xD800 | (code >> 10));
		glyph[1] = QChar(0xDC00 | (code & 0x3FF));
		return QString(glyph, 2);
	}
	/* character > 0x10FFF */
	return QString("");
}

The Qt developer tools get a 10/10 from me though. I say this mainly because glade runs like a slug at the best of times.

Making an XKCD-style password generator in C++

I’m learning C++ at the moment, and I don’t find long tutorials or studying the standard template library particularly fun.

Making this type of password-generator is not new, but it is a nice practical exercise to start out in any language.

1. Get a list of common English words

Googling “common English words” yielded this list, purporting to contain 5,000 words. Unfortunately it contains almost 1,000 duplicates and numerous non-words! Wiktionary has a much higher-quality list of words compiled from Project Gutenberg, but the markup looks a bit like this:

==== 1 - 1000 ====
===== 1 - 100 =====
[[the]] = 56271872
[[of]] = 33950064
[[and]] = 29944184
[[to]] = 25956096
[[in]] = 17420636
[[I]] = 11764797  

Noting the wikilinks surrounding each word, I put together this PHP script to extract the link destinations and called it get-wikilinks.php:

#!/usr/bin/php
<?php
/* Return list of wikilinked words from input text */
$text = explode("[[", file_get_contents("php://stdin"));
foreach($text as $link) {
	$rbrace = strpos($link, "]]");
	if(!$rbrace === false) {
		/* Also escape on [[foo|bar]] links */
		$pipe = strpos($link, "|");
		if(!$pipe === false && $pipe < $rbrace) {
			$rbrace = $pipe;
		}
		$word = trim(substr($link, 0, $rbrace))."n";
		if(strpos($word, "'") === false && !is_numeric(substr($word, 0, 1))) {
			/* Leave out words with apostrophes or starting with numbers */
			echo $word;
		}
	}
}

The output of this script is much more workable:

$ chmod +x get-wikilinks.php
$ cat wikt.txt | ./get-wikilinks.php
the
of
and
to
in
I

Using sort and uniq makes a top-notch list of common words, ready for an app to digest:

$ cat wikt.txt | ./get-wikilinks.php | sort | uniq > wordlist.txt

2. Write some C++

There are two problems being solved here:

  • Reading a file into memory
    • An ifstream is used to access the file, and getline() will return false when EOF has been reached
    • Each line is loaded into a vector (roughly the same type of container as an ArrayList in Java), which is resized dynamically and accessed like an array.
  • Choosing random numbers
    • These are seeded from a random_device, being more cross-platform than reading from a file like /dev/urandom.
    • Note that random is new to C++11.
pw.cpp
#include <fstream>
#include <vector>
#include <string>
#include <iostream>
#include <random>
#include <cstdlib>

using namespace std;

int main(int argc, char* argv[]) {
    const char* fname = "wordlist.txt";

    /* Parse command-line arguments */
    int max = 1;
    if(argc == 2) {
        max = atoi(argv[1]);
    }

    /* Open word list file */
    ifstream input;
    input.open(fname);
    if(input.fail()) {
        cerr << "ERROR: Failed to open " << fname << endl;
    }

    /* Read to end and load words */
    vector<string> wordList;
    string line;
    while(getline(input, line)) {
        wordList.push_back(line);
    }

    /* Seed from random device */
    random_device rd;
    default_random_engine gen;
    gen.seed(rd());
    uniform_int_distribution<int> dist(0, wordList.size() - 1);

    /* Output as many passwords as required */
    const int pwLen = 4;
    int wordId, i, j;
    for(i = 0; i < max; i++) {
        for(j = 0; j < pwLen; j++) {
            cout << wordList[dist(gen)] << ((j != pwLen - 1) ? " " : "");
        }
        cout << endl;
    }

    return 0;
}

3. Compile

Lots of projects in compiled languages have a Makefile, so that you can compile them without having to type all the compiler options manually.

Makefiles are a bit heavy to learn properly, but for a project this tiny, something simple is fine:

default:
	g++ pw.cpp -o pw -std=c++11

clean:
	rm -f pw

Now we can compile and run the generator:

make
./pw

The output looks like this for ./pw 30 ("generate 30 passwords"):

Downtime

Looks like one of Facebook’s webservers took a nap yesterday.

When they designed this error page, I wonder if they realised that both the ‘Help’ and ‘Go back’ links give you the same error again.

Pyrocket and Ubuntu

I have a great USB rocket launcher, it’s more useful than a computer mouse most of the time actually. I spotted a moth on the roof the other day, and hadn’t installed pyrocket on this computer yet.

A quick apt-get install pyrocket is all it takes to solve that though, right? No such luck.

Apparently, the quality control in the Ubuntu repos are such that this package has been broken for several months now, despite the dependency issue being fixed by an upstream fork.

So this is the error you get at the moment anyway:

Traceback (most recent call last):
  File "/usr/bin/pyrocket", line 17, in 
    from rocket_frontend import RocketWindow
  File "/usr/lib/pymodules/python2.7/rocket_frontend.py", line 11, in <module>
    from rocket_webcam import VideoWindow
  File "/usr/lib/pymodules/python2.7/rocket_webcam.py", line 2, in <module>
    from opencv import cv, highgui
ImportError: No module named opencv

The solution, beyond complaining about it, is to read the bug report here and do this:

git clone https://github.com/stadler/pyrocket
cd pyrocket/src
./pyrocket.py

But the moth had escaped by then.

Chromium B.S.U.

I was looking for the Chromium web browser and installed this mysterious chromium-bsu package at the suggestion of the package manager:

mike@mikebox:~$ sudo apt-get install chromium
...
Package chromium is not available, but is referred to by another package.
...
However the following packages replace it:
  chromium-bsu

All the most important discoveries were made by accident — including penicillin, teflon, coke, and what turned out to be a really cool space-shooter game.

Chromium B.S.U. screenshot

About an hour of clicking later— back to work. Installing the wrong package is a real time sink!

Changing the IP address on a Cisco switch

Cisco switches are a bit cryptic, but well documented. You can generally do just about anything with enough patience and Googling, but even simple configuration tasks can be difficult if you’re not familiar with the interface.

These are my 3 tips to configuring Cisco devices:

1. telnet
Always telnet or SSH to the switch to log on. Don’t use configuration utilities or a web interface, because everything that turns up on Google refers to the command-line interface.
2. enable
All of the fun options are hidden by default. Make liberal use of enable to do the serious work.
3. ?
Pressing ? pops up a list of ways you can finish off the current command.

For example: IP settings

The first step when moving around a used switch is to set it up for a different network. Here is where you will find the IP config:/p>

Log in:

telnet 10.1.x.x

Now enable the admin options:

example-switch>enable
Password: (type in your password)

Note that the prompt has changed from < to #. We can now change the IP and subnet mask of the switch:

example-switch#
interface vlan 1
ip address 10.1.x.x 255.255.x.x

Weird bug in iPad mail app

If you use the iPad mail app with Gmail, set up as an exchange account, you get a folder labelled [Gmail] which has your spam and starred mail folders.

But it turns out that an undocumented feature turns up if you also create a label called [Gmail]:

Labels and these special folders are different, so why are they getting mixed up? Or if they’re meant to be mixed- why are their two [Gmail] headings?

It seems to have slipped through the developers’ minds when they stored them in the same place, because the app behaves very inconsistently with this setup (variously duplicating labels and headings, and then leaving one list out of sync when changes are made).

Workaround: rename your labels:

I hope the developers are paying more attention next release- I’m only blogging this because there’s nothing much that google turned up about it.

Re-arranging columns on JTables

As I’m only new to Java UI work, I was surprised to see how easy it is to work with tables. A few things I’ve learnt:

  • JTables only show column headers if the table is placed inside a JScrollPane
  • It’s really annoying to drag-and-drop narrow, resizable columns, so turn off resizing.

I ended up with this user-rearrangeable table:

Basic code for this, thanks to Google Windowbuilder:

import java.awt.EventQueue;

import javax.swing.JFrame;
import javax.swing.JTable;
import javax.swing.table.DefaultTableModel;
import javax.swing.JScrollPane;

public class TableTestWindow {
	private JFrame frame;
	private JTable table;

	/**
	 * Launch the application.
	 */
	public static void main(String[] args) {
		EventQueue.invokeLater(new Runnable() {
			public void run() {
				try {
					TableTestWindow window = new TableTestWindow();
					window.frame.setVisible(true);
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		});
	}

	/**
	 * Create the application.
	 */
	public TableTestWindow() {
		initialize();
	}

	/**
	 * Initialize the contents of the frame.
	 */
	private void initialize() {
		frame = new JFrame();
		frame.setBounds(100, 100, 525, 520);
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		frame.getContentPane().setLayout(null);

		JScrollPane scrollPane = new JScrollPane();
		scrollPane.setBounds(12, 12, 460, 440);
		frame.getContentPane().add(scrollPane);

		table = new JTable();
		table.setFillsViewportHeight(true);
		scrollPane.setViewportView(table);
		table.setRowSelectionAllowed(false);
		table.setColumnSelectionAllowed(true);
		table.setModel(new DefaultTableModel(
			new Object[][] {
				{null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null},
				{null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null},
				{null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null},
				{null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null},
				{null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null},
				{null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null},
				{null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null},
				{null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null},
				{null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null},
				{null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null},
				{null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null},
				{null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null},
				{null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null},
				{null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null},
				{null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null},
				{null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null},
				{null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null},
				{null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null},
				{null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null},
				{null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null},
				{null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null},
				{null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null},
				{null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null},
				{null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null},
				{null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null},
				{null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null},
			},
			new String[] {
				"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"
			}
		) {
			/**
			 * Java coughs up warnings if we dont do this
			 */
			private static final long serialVersionUID = 1L;

			public Class getColumnClass(int columnIndex) {
				return String.class;
			}

			public boolean isCellEditable(int row, int column) {
				return false;
			}
		});

		for(int i = 0; i < 26; i++) {
			table.getColumnModel().getColumn(i).setResizable(false);
			table.getColumnModel().getColumn(i).setPreferredWidth(20);
		}

	}
}