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);
		}

	}
}

Moodlification

Moodle is an alright piece of software, but if you ever try to discuss code, it will mangle it and create a horrible mess.

To mitigate this, write your posts in HTML view, and paste your code as it appears after running it through this moodlify.php script:

#!/usr/bin/php
<?php
/* moodlify.php -- whitespace fixes for code to post on moodle */
if(!isset($argv[1])) {
    echo "Usage: " . $argv[0] . " [file]\n";
    exit(0);
}
$file = $argv[1];
if(!file_exists($file) || !$code = file_get_contents($file)) {
    echo "Could not open file: ".$argv[1]."\n";
    exit(1);
}

/* Series of find-replaces to strip whitespace */
$code = htmlentities($code); /* < > " ' etc */
$code = str_replace("\t", "    ", $code); /* Tabs (4 spaces)*/

$code = str_replace("  ", "&nbsp;&nbsp;", $code); /* Double-spaces */
$code = str_replace("\n", "<br />", $code); /* Newlines */
$code = "<div style=\"padding-left:4em;\"><code>".$code."</code></div>\n";

/* Output options */
if(isset($argv[2])) {
    file_put_contents($argv[2], $code);
} else {
    echo $code;
}?>

And moodle will never mangle your code again!

Update 2012-09-05 Corrected the above code due to out own CMS mangling the code, due to a workaround for this horrible (now deprecated) anti-feature.

Australian telephone tones

I made this set of telephone tones for an IP-PBX running asterisk. A lot of telephone-related audio files on the Internet seem to be recordings of actual beeps, so the quality is not great. Given that they are trivial to synthesise with Audacity, I made these:

To convert them to .ulaw format for asterisk (click here to download archive), I saved them as a .wav file and ran this bash script over the directory:

#!/bin/bash
for a in *.wav; do
    sox $a -r 8000 -t ul $a.ulaw
done

Making tones for other countries

Get a copy of the document "Various tones used in national networks (According to ITU-T Recommendation E.180) (03/1998)". It describes the tones used in most of the world. There is also a Cisco tones database, which has slightly different numbers.

Open up Audacity and use the Generate → Tones feature to make the first beep. Where there are multiple frequencies, make each frequency in different tracks, then merge them into a single track.

Use Generate → Silence to add the delay on the end, then continue from there

Afterward, you need to normalise the volume. Read the number from the volume Cisco tones database, then use Effect → Amplify, and set the new peak volume to the value shown — for the above tones this is -10 or -13dB.

HP Mini 210 review

I used a HP Mini 210 netbook for around 18 months. It costed just $329 AUD when I bought it, and had Windows XP and a 160GB hard drive.

I was originally interested in dual-booting Windows alongside Linux, but HP support proved to be very stubborn and would not provide recovery disks or the Windows licence key, so I ran it with only Linux instead.

Here are some features to note:

  • An SD card can be placed in the slot and stays out of the way.
  • The VGA port makes it suitable for doing presentations.
  • The battery life is not fantastic. Around 3 hours when new, and reduced to just 20 minutes or so by the first year.
  • Not particularly durable. The right-click stopped working on the mousepad after a while. I enabled mac-style gestures in Ubuntu to overcome this.
  • The ventilation is poor. It has no vents on the bottom at all, which is great for keeping the inside dry, but it has a very weak fan and does not handle heavy loads gracefully.

Some things you can do to improve it.

  • As soon as I found out how to open the case (video), I got an Intel SSD, which was quite expensive, but can be used in whatever netbook you have. It makes it quieter, faster, more power efficient, and removes the shock-sensitivity that plagues notebook hard-drives.
  • Consider getting a high-capacity battery. I replaced the dead standard size one with a cheap 3rd-party battery (link), but it could still do with more power.
  • The default install is filled with crapware. Either reinstall windows or run something else. GNU/Linux compatibility is great, and it also runs Windows 7 with no worries. It turns out your Windows XP key is inside the case, so open it up and use it.

It was a good laptop for the price, but not exceptionally fast, durable, or long in its battery life. This netbook should be purchased with the understanding that it will have a short life.

Alphanumeric phone numbers

Some popular phone numbers (eg. 1300-FOOBAR) are not numbers at all. If you are running a VOIP server then you may be interested in this snippet of PHP code to convert them into actual numbers, allowing users to dial by typing the more familiar form.

function normaliseTelephoneNumber($start) {
	/* Return an extension with numbers substituted in place of letters for dialling */
	$map = array(	"A" => "2", "B" => "2", "C" => "2",
			"D" => "3", "E" => "3", "F" => "3",
			"G" => "4", "H" => "4", "I" => "4",
			"J" => "5", "J" => "5", "L" => "5",
			"M" => "6", "N" => "6", "O" => "6",
			"P" => "7", "Q" => "7", "R" => "7", "S" => "7",
			"T" => "8", "U" => "8", "V" => "8",
			"W" => "9", "X" => "9", "Y" => "9", "Z" => "9",
			"+" => "+", "*" => "*", "#" => "#");
	$new = "";
	$hasnumber = false;
	$ext = strtoupper($start);
	for($i = 0; $i < strlen($ext); $i++) {
		$c = substr($ext, $i, 1);
		if(isset($map[$c])) {
			$new .= $map[$c];
			if($hasnumber == false) {
				/* No numbers before letters */
				return $start;
			}
		} else if(is_numeric($c)) {
			$new .= $c;
			$hasnumber = true;
		}
	}

	if($hasnumber == true) {
		return $new; /* Return numeric version as appropriate */
	} else {
		return $start; /* Leaves full words like "joe" or "bazza" unchanged */
	}
	return $new;
}

Note that this will only alter the number if it begins with numbers. This is to make sure that (at least in my case) the local network extensions don't get messed with:

echo normaliseTelephoneNumber("1300-FOOBAR")."n"; /* 1300366227 */
echo normaliseTelephoneNumber("mike")."n"; /* mike */

Introducing BigInteger

Most ordinary-scale programs don’t use very large numbers, but today I’ll show you how to use Java to do some more serious calculating, with the Fibonacci numbers as an example:

0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, …

The numbers in this sequence always get larger, so you don’t need to be a software engineer to see that int and long wont be big enough. Just to highlight the problem, here is a naive approach to listing the Fibonacci numbers:

class FibonacciLong {
	static long solutions = 0;

	public static void main(String[] args) {
		long num1 = 0;
		long num2 = 1;
		long tmp;
		solution(num1);
		solution(num2);
		while(true) {
			tmp = num1 + num2;
			solution(tmp);
			num1 = num2;
			num2 = tmp;
		}
	}

	static void solution(long number) {
		solutions++;
		System.out.println(solutions + "t" + number);
	}
}

The output of this program overflows after the 93rd term. Java only uses signed integer types, so we end up with a negative number:

1	0
2	1
3	1
4	2
5	3
6	5
....
92	4660046610375530309
93	7540113804746346429
94	-6246583658587674878

To find larger values, we can use the BigInteger class, under java.math. They are constructed like this:

BigInteger num1 = new BigInteger("0");

They don’t really work like the primitive types. They are manipulated with methods like .add() and .subtract(), rather than the operators that we are accustomed to: + - * / %.

The below code has been written with BigInteger instead of long. It will return the nth term of the Fibonacci sequence, or enumerate solutions until the cows come home.

import java.math.BigInteger;

class FibonacciBigInteger {
	static long solutions = 0;
	static long find = 0;

	public static void main(String[] args) {
		if(args.length >= 1) {
			find = Long.parseLong(args[0]);
		}
		BigInteger num1 = new BigInteger("0");
		BigInteger num2 = new BigInteger("1");
		BigInteger tmp;
		solution(num1);
		solution(num2);

		while(true) {
			tmp = num1.add(num2);
			solution(tmp);
			num1 = num2;
			num2 = tmp;
		}
	}

	static void solution(BigInteger number) {
		solutions++;
		if(find == 0 || solutions == find) {
			System.out.println(number.toString());
			if(find != 0) { System.exit(0); }
		}
	}
}

The good news is that this program is now capable of working with numbers too large to say aloud. So today is a good day to start using Unix’s wc program to appreciate how big these numbers actually are:

mike~$ java FibonacciBigInteger 420000 | wc -m
87776

Subtract one character because there is an end-of-line in the output— and this means that the 420,000th Fibonacci number is 8775 digits long. We have discovered a new fact!

Note: There is still one variable in the program above that will overflow eventually, so you should also convert that to a BigInteger if you are hoping to calculate past about the nine quintillionth Fibonacci number (which you would be crazy to do with this method).