Index: .hgignore
===================================================================
--- .hgignore	(revision 442)
+++ .hgignore	(revision 758)
@@ -9,2 +9,5 @@
 src/mlx/gui/dcdata.py
 xplra.py
+chromedriver.exe
+debug.log
+moodlesetup.txt
Index: .hgtags
===================================================================
--- .hgtags	(revision 724)
+++ .hgtags	(revision 734)
@@ -21,2 +21,3 @@
 47742f309187a06087d9cb385862ee42b213b119 version_0.35.2
 ec0a3d264b30138f758cc5d67e4cd44452e6cff9 version_0.36
+e1da02e929ed126dd66ced647bff816a3ba405e5 version_0.37
Index: MLXMANIFEST
===================================================================
--- MLXMANIFEST	(revision 726)
+++ MLXMANIFEST	(revision 735)
@@ -4,5 +4,5 @@
 library/xml/dom/xmlbuilder.pyc	15414	42285fdd810a2aa92c06775b5d415c33
 library/fnmatch.pyc	3414	4d50000f78e903598af06ef1c5cfb3ef
-library/gtk/_gtk.pyc	549	9458c6b0ddc3c1e11c1203b6fd4633a9
+library/gtk/_gtk.pyc	549	e8970604019edd7560e58d0cc8862bd8
 library/encodings/cp858.pyc	7591	02054a0b8e91b9758826eb70cddf096f
 library/encodings/cp1254.pyc	2678	17ff2ac86f90886d5c73ed789f0499f3
@@ -16,16 +16,16 @@
 library/distutils/__init__.pyc	365	6f7d10b79fa1086d3ac9ce0806b5ba23
 doc/manual/hu/pirepViewer.png	26835	267e4325e550ed14ed22f86a2c7e8e9d
-doc/manual/hu/prefsGeneral.png	25082	9775eaf0c8f1f770e1ad46ce9b6e6973
+doc/manual/hu/prefsGeneral.png	25601	a33faad4d576b080ae0c040f5a1a5404
 library/mlx/gui/statusbar.pyc	4812	490a882b53429fa18f317e146f0c70c0
-doc/manual/hu/mainWindow.png	27770	42e5565ccb75d22b2f378d2df2b2865d
+doc/manual/hu/mainWindow.png	29112	ae56b6aa507c98c31e370a37df846362
 library/lxml.etree.pyd	2977792	d9f46391d4eeb719304a07b1ddaa72d9
 library/pdb.pyc	41336	cca4477d622e897335b3f0cecb667ff4
 library/lxml/html/_setmixin.pyc	4312	eac09282d97aceede13bb8efc0da3a5f
-doc/manual/en/fuel.png	27700	777f9f3431c2f53db4b98f3449e4baf7
+doc/manual/en/fuel.png	29849	13c584d187765e22821abb492ab565d5
 subprocess.exe	267264	00107a8ed63a3e055e5d14f5c4b6cb0d
 library/selenium/webdriver/android/webdriver.pyc	1348	d8e382aa629964bf424c14159dd4fd2d
 library/xml/sax/xmlreader.pyc	17862	0362ded7016d21cdd85aea1170ebdda1
-doc/manual/hu/fuel.png	29886	92a67f7d11e5101a7ac64034c7be26a4
-doc/manual/hu/time.png	28618	50dd8f862b7e1034cb1066ab16e7c587
+doc/manual/hu/fuel.png	32346	01ea98e0302ddaad608e468d2c590d7b
+doc/manual/hu/time.png	29089	073d4efd85dfe576a800406990ece2ed
 library/encodings/mac_croatian.pyc	2755	425b44ba614eff660b863cb7cc12e509
 library/stringprep.pyc	14089	1c0d248fa5ff974c44479a82d8deb72c
@@ -41,5 +41,5 @@
 library/encodings/big5hkscs.pyc	1670	0ff54e70f21b5e4a359d3779367cba85
 library/encodings/utf_8.pyc	1806	4b8dee0284ea2f55c4142fcde7463524
-locale/hu/LC_MESSAGES/mlx.mo	73166	f479bb641fede1c31adbd0ff2a3d736b
+locale/hu/LC_MESSAGES/mlx.mo	73229	95361f226162895b4aef044832434cda
 library/encodings/mac_roman.pyc	2716	4b4745eccfcb77860b70b374ac07fbcc
 library/py_compile.pyc	6292	425da7ee065aa158fd274946204a4a77
@@ -60,5 +60,5 @@
 library/base64.pyc	10528	39abec9c60dc011d3c763f443f9b1a11
 library/mlx/gui/bugreport.pyc	4602	a32ca4d644f316b3d120ae3dc2597eba
-doc/manual/en/flightSelection.png	29847	7817c8f5ed8d7fe64cf72d0addaf3e4a
+doc/manual/en/flightSelection.png	26869	35427845ae09baa263ab757d0e0dfa50
 library/encodings/cp500.pyc	2639	bb122a6e1384530e1b2efb14cf999239
 sounds/TaxiAfterLand.mp3	155481	ded1908d2e7bf2ee2cbf445bb44cf756
@@ -66,5 +66,5 @@
 library/json/encoder.pyc	12918	d4d1f0b8a67df3e7c2f470ca19d0f964
 library/selenium/webdriver/remote/webdriver.pyc	31960	5ef4044402659e32409fa4570a91f03c
-doc/manual/en/mainWindow.png	25345	15b206118a6670f9edaceb8aa830fdd7
+doc/manual/en/mainWindow.png	26604	6962cfbd1db32efd06ebbb45a3223109
 library/encodings/iso8859_3.pyc	2685	7b5b2924647578ab7555db2d86cd1b9b
 locales/kn.pak	15411	d24e6fbfcbd4c8e76cc534bf63d75d61
@@ -72,8 +72,8 @@
 library/email/generator.pyc	9877	f387feb2b59e121f7b0ece209e8875ae
 library/types.pyc	2386	25023bf7b2334153cfff8922979862c9
-doc/manual/en/bugreport.png	11730	a649c456acbbfa0419d183a7b8261c01
+conn_grey.png	988	7a956ebba793197f324cec56d94073e7
 locales/mr.pak	13137	cdfba7767a139ab7c34ec01d496029a2
 library/xml/dom/pulldom.pyc	12238	14c378c017da4b4a1576321c884c9ac4
-doc/manual/en/landing.png	26272	97115671017d7dd3af55513ae912ff17
+doc/manual/en/landing.png	28340	8d467c12e21c9b71d6d6cfc8941b8ec0
 library/textwrap.pyc	11410	f7bfc56465324edf71b977397a4c7c0b
 locales/nb.pak	6676	56894a866d7af9193faca13ef28c7b68
@@ -86,5 +86,5 @@
 logo.ico	44054	8a2bfc719521f8dc59540055941af7db
 library/string.pyc	18988	2347037ee5f3cf58afc10643e633d975
-doc/manual/hu/takeoff.png	28255	c328eb866f78338e178f1fb761f57511
+doc/manual/hu/takeoff.png	30058	74b1ec31eb1f5e61984a2fc8df2105d1
 library/encodings/rot_13.pyc	3413	04994d9c6c3622213b73a23e86c4cd07
 library/selenium/webdriver/chrome/service.pyc	3456	213d552983fc36a04948f1c1fb551965
@@ -102,12 +102,12 @@
 library/encodings/cp949.pyc	1638	a248191502a186c4ee64598e5e684608
 library/urllib.pyc	48771	4d4b07040a1d0b0b31f5779eeb01c269
-doc/manual/hu/payload.png	29785	c9b8f7dd269629ea22764f531e5d27bf
-doc/manual/en/gateSelection.png	28063	b02c97fe05d9cd8ea986a5023937f4bc
+doc/manual/hu/payload.png	30209	626ae1a3c1e7b62ae77356aaf83e5376
+doc/manual/en/gateSelection.png	30407	f7e142a9facf8e76b4629405adaa8520
 library/UserDict.pyc	7920	c72896a5a8a1a3d93fa6ba23c50a04b0
 library/encodings/koi8_r.pyc	2700	fff1b35002b9e938b7ea6fd0fbe824ae
 sounds/737taxi.mp3	157362	846c403e5cb660be53c8b6da16b68234
-library/mlx/const.pyc	6294	5cdef68da98c1d03135d20e2b78668fc
+library/mlx/const.pyc	6289	088e910fb056e2774248970d685a99f8
 locales/th.pak	13637	7262d0f5e1877212a45a1ef1ccc4c777
-library/mlx/gui/cef.pyc	13834	c0aa2cc0103680c5c8f25a8b504839d8
+library/mlx/gui/cef.pyc	13834	0c210ce9cb47acd967cc823184dc4209
 library/encodings/punycode.pyc	7570	335ff94e207934ae64e6f5fc07a5cacc
 mlx_cef_caller.bat	142	d1815645d52f5638f27f16903a977d00
@@ -126,5 +126,5 @@
 library/share/locale/hu/LC_MESSAGES/atk10.mo	8465	e3f3466da367215aad971d7b0394229d
 library/selenium/webdriver/android/__init__.pyc	126	2c08539a10f0de9d77dea0fb81f88c93
-library/select.pyc	543	000aafbf9f916aa8dc56f48e6421ae8e
+library/select.pyc	543	ad592b6be3ddc27064fc34dff8516906
 library/encodings/uu_codec.pyc	4646	5282a5fcb676851224d2924aa3e48a9e
 library/selenium/webdriver/remote/file_detector.pyc	2025	06b01ab65d8732125257374fb263e879
@@ -138,4 +138,5 @@
 library/random.pyc	24831	54b2048974a5c98912276ffa5dc637a9
 icudt.dll	9956864	5434e18b933e03f274d8da59fda4c676
+doc/manual/hu/simbriefSetup.png	32497	b287886107be21b6695897085b218fd4
 library/mlx/soundsched.pyc	9185	4211730170fddb82b5683ce1849234ee
 library/mlx/logger.pyc	13109	a70c061d36cf6b4e540f5610133466d3
@@ -148,5 +149,5 @@
 library/re.pyc	12699	5b157b40e294465c0f3d2ca82f70f66e
 locales/gu.pak	13531	b227db903a5cadbf928e97133643415d
-doc/manual/hu/flightInfoTab.png	28786	3e8af27775131f02696b0912ca441f36
+doc/manual/hu/flightInfoTab.png	31371	afe2829f357d2c3fc1e726f69fc7e711
 library/xml/dom/expatbuilder.pyc	31399	c996bf83ee6078c9ddda33b79ce11d48
 library/encodings/mbcs.pyc	1883	2213911a7dd0deada6799b63e18b695b
@@ -167,12 +168,12 @@
 library/selenium/webdriver/chrome/options.pyc	5251	e62df837d9fe50d3f6464a0c4f02489e
 library/encodings/cp424.pyc	2669	4667015363f54bae44e98d4748e8acfa
-doc/manual/hu/flightSelection.png	30815	e8bcdda6079c6b16ffa72ca62c21ca85
+doc/manual/hu/flightSelection.png	28213	5aaf988630d39ebf7b51ecaeb0c2ca24
 library/win32pipe.pyd	24064	de374eded459293b2a7eb8ed8fb38eaa
-library/win32pipe.pyc	552	91891e843acff73f0bf28e2450464e7b
-doc/manual/hu/cruise.png	27460	a2610173563e45baae1b0f9e63007ebd
+library/win32pipe.pyc	552	639fadb461731f169e36c2ae993e2f2e
+doc/manual/hu/cruise.png	27925	f7045d9150a756f487e3ae3d08b4c1a5
 library/encodings/iso2022_jp_2004.pyc	1723	66376e788f3819e781897268c1100986
 library/cefpython3.cefpython_py27.pyd	968192	58161b73feae974f560d2c9f416adf3b
 library/encodings/cp866.pyc	8103	f36418dcf93b17b3ced63bd544f44159
-library/gio/_gio.pyc	549	2965d8aa7312527dda6888c8f5ecb287
+library/gio/_gio.pyc	549	b2ba51e7311cb543818e93939ea8d420
 library/encodings/cp437.pyc	7874	63f1278c0a66129c6fb4f504d6b61b66
 doc/manual/hu/prefsSounds.png	22667	452c396c2bda9684bf052c98fd7870d1
@@ -183,16 +184,17 @@
 doc/manual/hu/pirepLoaded.png	10573	af950f8f037d6e17a24edcb3e849d159
 library/pygtk.pyc	1914	814f68243c9fd447c0aa02326f1ead2b
-doc/manual/en/cruise.png	26531	fc94f27a40dad772437494c71e3c3d73
+doc/manual/en/cruise.png	26855	e209db02a066252f544549100d6a1e29
 locales/es.pak	7713	1bdcb1f825bf2bf68380662427f583b2
+doc/manual/hu/acarsTab.png	305091	489953210460410f2ec9d281b7ad6868
 library/encodings/utf_32_be.pyc	1747	3e8c9ef8145de502320202b1688fd2ee
-library/cefpython3/cefpython_py27.pyc	600	5aecdbd2ba6e2bcdc1bd808985d7825a
+library/cefpython3/cefpython_py27.pyc	600	097c550cc7e8274086f98b42644ba588
 library/encodings/mac_romanian.pyc	2756	a3b2a0892236bc80c8efe8940e33339e
-library/_ctypes.pyc	546	481eb87be1fe7c202bd442fdd963f41a
+library/_ctypes.pyc	546	c6f65e92190203fbce71595fe4bd58c2
 mlx_cef_caller_secondary.sh	178	ecb35d3294a5d1e729697f802a75cde4
 library/encodings/iso8859_2.pyc	2678	da65f42e1be5bec97161e8c1cc4f0fb7
-doc/manual/hu/briefing.png	31774	e090e1a6fab300a2d55cac03ad71d551
+doc/manual/hu/briefing.png	32976	48904e5a5ef9e05a5c87663a520d4aa5
 library/encodings/cp855.pyc	8071	0a4259ba854c24186f2ce61d85883b4a
-doc/manual/en/time.png	26508	0267dbdd59e65692bb0b9108103c07f5
-doc/manual/en/flightInfoTab.png	27718	4634da26b64d2c1a07ff5a7eec1ae29f
+doc/manual/en/time.png	27845	9e5b71b24e0b20dfa5e8e692ac3d2a2a
+doc/manual/en/flightInfoTab.png	30556	c2cf3cba37ffbfbf247cb91f27139a3c
 library/libgio-2.0-0.dll	1222182	ea1263fb4c2230284f3e30c446bfea6b
 library/encodings/cp037.pyc	2639	2981678e762ba857e30becbe27eb6614
@@ -204,5 +206,5 @@
 library/selenium/webdriver/common/action_chains.pyc	12050	696113f4eb85587998c08b915d8269a4
 library/encodings/latin_1.pyc	2111	52427573ccbfd06220b67d5a9dd1376e
-library/mlx/update.pyc	16925	8c57d867b7f8ed088ab2b2afb2ae455c
+library/mlx/update.pyc	16925	5b250a54fb5430cc1af93b01623fae64
 library/selenium/webdriver/firefox/extension_connection.pyc	3145	5f8929a22a172a0aa71d4d4acb2aaf17
 library/selenium/webdriver/remote/webelement.pyc	17884	6fa60da9a8bc1d2dc6e1286d9bd8f1ab
@@ -211,10 +213,10 @@
 library/unittest/case.pyc	37758	1d2235e74c246b562c5c00ddf73c621a
 library/subprocess.pyc	38648	c9f749db06dfc9fb32079de51d6efd01
-library/mlx/acft.pyc	40830	effafce121013f2b7631e46bf568b2ac
+library/mlx/acft.pyc	40830	a4b80fe5ea817bada60f2bb58642786b
 library/selenium/webdriver/firefox/firefox_binary.pyc	7758	cee737a6fd574b0f2e6ca28508844a30
 library/gobject._gobject.pyd	113152	3d05dd191361f83aa247b62c700fa04e
 doc/manual/hu/checklistEditor.png	27863	8c750892a52dc10ca654a9d0b2765358
 library/bisect.pyc	2971	ad8ce3e265b404ed3971b842106f8179
-library/mlx/gui/acars.pyc	1057	27238740a8776e6d8e6c8867d23955ea
+library/mlx/gui/acars.pyc	1057	27e9bc0d61654b3ac3b2a12fbda90e89
 library/gtk/_lazyutils.pyc	2772	93f9aa41ad4223106d99340cd539a566
 library/mlx/mlx.pyc	2979	876ff2c4456985567d8a8af6d156b743
@@ -222,5 +224,5 @@
 library/encodings/cp864.pyc	7999	ca90afae086cb6b8f69b8419cc3cd1cb
 library/pyexpat.pyd	152576	5a82e9ec95005aca6130751e5b961cc3
-library/pyexpat.pyc	546	bc188a96c9c3a69312eb9fb5d9183558
+library/pyexpat.pyc	546	42fbab8dc96e7ac91259a952f1bcbcce
 library/unittest/loader.pyc	10884	591a785be23fba35582ce33bdd3d7acd
 library/ftplib.pyc	32043	5a1925388a0874e459aed6891f4cf0c1
@@ -230,5 +232,5 @@
 library/unittest/suite.pyc	9838	bf2c79bd4a6b6032f1ea53fa4908748f
 library/encodings/mac_arabic.pyc	7824	820deef79b02e1f7e52eb18e9be655bf
-library/mlx/gui/prefs.pyc	27921	24af38ec7e9b5dd275557147785f3d74
+library/mlx/gui/prefs.pyc	27921	e2e0ae1a285ce62928539dbe8e61d6ff
 library/encodings/tis_620.pyc	2748	5969b4db4f967b11ae7992cdfbf5890e
 library/encodings/base64_codec.pyc	3568	1d8f13d09024bd124a9ff73a9244c0f1
@@ -237,5 +239,5 @@
 library/distutils/spawn.pyc	5292	4cbe559081b3f8cab915a083bd9ef4bd
 sounds/ding.wav	80856	18e639792d3767436ac6955eb60e4f54
-library/selenium/webdriver/firefox/__init__.pyc	126	0ce57f6223e88783f1a5d4d8c4998682
+doc/manual/en/simbriefResult.png	32136	c55b3f649ca19cbbef738feaee26d639
 library/mlx/checks.pyc	62721	759425ec78c8430f699691e5abf92c1d
 doc/manual/en/statusIcon2.png	5875	71fc121823dee0a4c00715fac1b5665c
@@ -277,6 +279,6 @@
 library/encodings/iso8859_10.pyc	2693	5e6cc8f721486ddbc1c99da285e13191
 library/mlx/gui/statusicon.pyc	5616	03e079307c1f83f36284e1b32887b28e
-library/mlx/config.pyc	32743	5a5523901d6fcb215f84e3d217c47f31
-library/atk.pyc	534	2a53b13519b7419e8cf08f48baa4c1b6
+library/mlx/config.pyc	32739	460aaf61cd13bc34bb91fdc2fd70d710
+library/atk.pyc	534	42a8ab11f346bb57ab20ad063225d6a6
 library/gio._gio.pyd	263168	acb8c95fdc391079bebdf7d8685b0dbc
 library/encodings/cp861.pyc	7868	c97a8970ce4d9f40cbafbea6576fbdca
@@ -286,5 +288,5 @@
 library/__future__.pyc	4177	43d264d951ac2085334c7b9dd4558595
 library/optparse.pyc	51253	39594a40bc1222173d57b7d5740e3d92
-doc/manual/en/takeoff.png	26128	63fb0227347eba88fe4f108d730dc8c0
+doc/manual/en/takeoff.png	28315	687ea09a056dbf29d8b8e995b691a6fa
 library/selenium/webdriver/safari/service.pyc	2695	e70fc7f74f77a3824804835b54bcfea4
 library/cairo._cairo.pyd	69632	46d708899832abc0123c4e9cb4f61d5d
@@ -298,9 +300,9 @@
 library/gtk/deprecation.pyc	3104	2e6c9eef82dfa8aa3beb05abd3e274a4
 Microsoft.VC90.CRT/msvcm90.dll	224768	4a8bc195abdc93f0db5dab7f5093c52f
-doc/manual/hu/route.png	26601	fd1ee7c47290f4c3a2f9fd6618aeb678
+doc/manual/hu/route.png	27514	be2b8fb4dc607e8216029d705007bb56
 chromedriver.exe	5009408	d662fef51894e4e5b6a6f508ed214904
 library/encodings/iso8859_7.pyc	2686	db390f1961c1fd4181e351c330816ecd
 library/xml/__init__.pyc	1058	8f77f254e8c6db7a3b6d9930a686d787
-doc/manual/en/finish.png	28855	671eac74a9f5801c794f08d5861eafaa
+doc/manual/en/finish.png	31484	0a5339d4127ca031231be6714e57137b
 doc/manual/hu/bugreport.png	12385	c71c7db56f19870ae5d6bb7f533d3679
 locales/pt-BR.pak	7211	d0542b9b325ea58be590d021aeb7333d
@@ -310,9 +312,9 @@
 library/xml/dom/minidom.pyc	61068	74ffa387e505b9fec2f2ad6a5dcd415d
 library/selenium/webdriver/chrome/__init__.pyc	125	ff98e760d2c777eacfc473761486a702
-library/mlx/gui/gui.pyc	54583	52c366560eec66413e5e7850fb5b3308
+library/mlx/gui/gui.pyc	54574	875895d2039b15e40d0d5b29ebb8e563
 library/stat.pyc	2503	d3b6dc7a54e705219f04d0be75a212de
 locales/ru.pak	10392	1b66f03dd656a2433ce643d0259fca6f
-doc/manual/hu/landing.png	28525	2dd99249a389e9faf5c4d7b1180e8ebb
-library/gobject/_gobject.pyc	573	e4e42f72e063e30757a0183f9b06fe39
+doc/manual/hu/landing.png	30425	a6c71e05b39a691729a2396514e7a16f
+library/gobject/_gobject.pyc	573	6d7b571de1872ff1398285072de22348
 libcef.dll	38585344	4dfb8a77f35c41bdbe8469877d4af853
 library/encodings/utf_7.pyc	1747	2beeb059236527197d6c640396476ce8
@@ -321,17 +323,19 @@
 library/email/charset.pyc	13200	771aee9802116e673d08b44bc0450f99
 library/libpangocairo-1.0-0.dll	104729	d3ad38599649097a3645777ff95be08e
-doc/manual/en/briefing.png	32129	65cf1236d009fd61c1bd6c374ffd261c
+doc/manual/en/briefing.png	32215	60c6ca0af346ec1e399801834bb33036
+doc/manual/hu/simbriefResult.png	32291	9586bf2955d70cf0a3b7309747d3a535
 library/uu.pyc	4212	0073b65d26a6092e4e177c7763058fa4
 library/encodings/iso8859_1.pyc	2678	f09ec3b7f6a348cbd7f7286b60572bd0
-doc/manual/hu/monitorWindow.png	20577	34bfd26fe50e72706625a9203ac98217
+doc/manual/hu/monitorWindow.png	21499	c8296e51e14588ac5b037b4832161dfc
 library/pangocairo.pyd	17920	5fc69a7d29a6d3127710c64de440daa4
-library/pangocairo.pyc	555	d83b002b1ec69315516867733a206a60
+library/pangocairo.pyc	555	d027670b8f45671d7d39fc258aed018a
 library/zipfile.pyc	37345	568c6ee7e0404625f47248340afb4524
 locales/it.pak	7188	10ae10d8bfcffbfa1160574134e14edb
-doc/manual/hu/helpTab.png	27961	8e7cc4aeb8d7f40fcf186c7caf53583b
+doc/manual/en/acarsTab.png	198944	3ec162b7e6b077ab62d673c335929fb3
+doc/manual/hu/helpTab.png	29126	88f236f84b1b520c60653f4665fd10e2
 library/sre.pyc	499	92bb633daa0bab6550ec0e3677673b5b
 library/tokenize.pyc	13565	354d02cd79df187a28c1851a1e3bad96
 library/win32con.pyc	140797	84d2d064066cfbcc219230f7e9c6e0b0
-library/mlx/gui/flight.pyc	147639	f487da6516256651fdc2410dac2ed76e
+library/mlx/gui/flight.pyc	147646	de6b80a55be3e8a2c74b1b85f29f219f
 library/xmlrpclib.pyc	40724	d4f819f9199ea21236a74c967e335c43
 library/lib/gtk-2.0/2.10.0/engines/libwimp.dll	97984	a5bbbe46e1363aba9020a76f2d58e9d6
@@ -339,5 +343,5 @@
 library/copy_reg.pyc	4893	7a58bc441232056d1c123cd3375b3774
 library/libgtk-win32-2.0-0.dll	4938115	cc6dca192aca6f91bd96a5b5f86e036d
-doc/manual/en/debugLogTab.png	33696	5be65d41058f78554840032c51aa9d6d
+doc/manual/en/debugLogTab.png	32755	c1262010590be018d6fb8fe65a8ed7cd
 library/dummy_thread.pyc	5094	310a3d15c9cf20259b0ca4892811dec5
 library/gtk._gtk.pyd	1882624	bbb1d1e41bbebc6abf69bed719d50497
@@ -345,9 +349,9 @@
 locales/fi.pak	7098	70c7e2fb26c7abf09cb0f9972fb6c93a
 doc/manual/en/monitorWindow.png	20886	42e08d10c758e21209c4c021f391dee7
-library/_hashlib.pyc	549	d4aff375686d5501893ac1116dbaf7a1
+library/_hashlib.pyc	549	a5a3dd83f8fea0ac6182176585fa46fe
 library/copy.pyc	11674	30d1745c5e38ab170f8a0486fbebf305
 locales/et.pak	6856	18a0a857e46c28b7f1cd8ff639a24aec
 library/unittest/result.pyc	7444	aefbb3cc79c0ff17787ce84faed7f5e8
-library/mlx/gui/__init__.pyc	107	22f268cac1c5be2f3a058810b6caead3
+library/mlx/gui/__init__.pyc	107	3494fc6a671a7b8a9dc72ea93948ae52
 library/mlx/pirep.pyc	4623	6c485ea65ff6245ab56960a8bbec7051
 library/json/scanner.pyc	2169	e19ee185b7c79ab5cc433b39fcd33faa
@@ -358,5 +362,6 @@
 library/encodings/iso2022_jp_3.pyc	1699	ee1c3aa11bbe19eeb4deafa238563133
 library/cefpython3/__init__.pyc	556	38a652a0209e49e323bee4dd54a018af
-library/mlx/common.pyc	1300	9c6f2d502b9274073cd96caaf4fb90ff
+library/mlx/common.pyc	1300	3967399a64f2ad045d07a87aec5b12a2
+doc/manual/en/simbriefSetup.png	30414	9788762f9a23ffd71a66cbd90a23f13b
 library/ctypes/__init__.pyc	18846	2bd183962453ae93d6887b303d3fd338
 library/mlx/acars.pyc	1690	733d057c94683c74079d6e096f601855
@@ -365,5 +370,5 @@
 sounds/taps.mp3	16614	d4a019c7e9342ae329aee6932d6a43c3
 library/markupbase.pyc	9010	b33ff430e9b1adba3d540b5394ff289a
-library/cairo/_cairo.pyc	561	fb6b7b442e46dce7a08d5875101f4a21
+library/cairo/_cairo.pyc	561	d334f604caf04c5f899c0fa41d1f0c43
 locales/te.pak	16110	c3af85e60b78273a93970cf8805ded04
 library/lxml/cssselect.pyc	3861	2e693f0c25ab04b1d130646cddd4e081
@@ -377,12 +382,12 @@
 library/mlx/fsuipc.pyc	74670	4da0a45a9a6372a2e463ab21fdc43359
 library/xml/sax/__init__.pyc	3579	c78222ad1fd4f3dbdea06666fe74e84e
-doc/manual/en/connect.png	27436	5406dfe5408569cb95499c057bf0185b
+doc/manual/en/connect.png	29728	191520526caf0fa58efcd97d597c9247
 library/selenium/webdriver/opera/service.pyc	2411	df01b6f7348ad54da47919158fe7e596
 library/selenium/webdriver/opera/webdriver.pyc	5308	7ba71ef197d7d537635427f166da0f1c
 library/encodings/charmap.pyc	3203	2358b8eb05c66e2a2826b5db6f214547
 library/libgdk-win32-2.0-0.dll	931390	a2b46daac3d4140efdf86429f7aea476
-library/mlx/gui/common.pyc	10433	e96ada4071fcdd2937398627d5d281dd
+library/mlx/gui/common.pyc	10433	591658e014f48a4e6592aed9cd30f0bc
 library/dummy_threading.pyc	1265	5ab5c897721aeb346b5bccb84066c5bd
-doc/manual/hu/index.html	51903	4bfbb26709c699f21fe593cb4efd2d98
+doc/manual/hu/index.html	57499	d169091b33dc0505b6f123c4c4df854d
 library/encodings/cp1258.pyc	2681	5c09b3b0a82036b0e785422376cb64cc
 sounds/767taxi.mp3	160496	4cb43cda6e1214fbe15328a716827345
@@ -391,20 +396,20 @@
 library/calendar.pyc	26136	6f2f18efc99d759f99b2935b9dc9dbb1
 library/encodings/aliases.pyc	8750	75c22196420d0157fec085d285947a71
-conn_grey.png	988	7a956ebba793197f324cec56d94073e7
+doc/manual/en/bugreport.png	11730	a649c456acbbfa0419d183a7b8261c01
 library/HTMLParser.pyc	11589	f641df5331b5bb552ebe180f79b85a6b
 library/bz2.pyd	76800	1ca9ececd2c84b80996fe632627f857f
 library/encodings/iso8859_16.pyc	2695	85e819a29dec2b601ce978dcd5a040fc
 sounds/cpt_takeoff.mp3	8255	254edcf79b8b3752d1a7c191c57f615a
-library/bz2.pyc	534	950c3961b75846807cc186203935c92d
+library/bz2.pyc	534	61154b8608594e87a5d5affd53237fb9
 library/email/utils.pyc	8840	a7be428df6fddee6121cfce55382d1f8
-doc/manual/en/prefsGeneral.png	24487	4858becb6764518a428273e5ee8ee4d5
+doc/manual/en/prefsGeneral.png	25633	a5ef76b874f4ad5384b519a11bdf9c47
 library/encodings/iso2022_jp_2.pyc	1699	4b917caa926e5779176612409c66be39
 library/_ssl.pyd	720896	90826a140137efd0d806038828e16f8e
-library/pango.pyc	540	4a4e27fa2068680ba2a67bb99132370a
+library/pango.pyc	540	83b3f7d1048b264980bb06583565f521
 locales/de.pak	7420	18ba99b07536017ed8b87cb4f77aee1c
 library/glib/__init__.pyc	187	d44c8c2772b263a36726ac0dcfc04b42
 library/pango.pyd	111616	c934763bdf125c3dc92b56db817001d4
 library/libgobject-2.0-0.dll	341594	356d697647a480562c4e2e921b13f8ed
-mlxupdate.exe	18944	b8bf9f3d22348aa1f78727f81d967f93
+mlxupdate.exe	19456	d7ee6976cf50ad2cee274eb124457588
 library/email/errors.pyc	3229	cdb78ab237e195ad69f059128bc03b1d
 library/linecache.pyc	3091	666e6f81b0ea9a7637f8068493f738d5
@@ -416,5 +421,5 @@
 library/getopt.pyc	6454	8f8cd6763f5339988d157d153c910bf5
 library/encodings/cp857.pyc	7611	1b46c8959ac097b73e1c680d464cdc32
-library/_ssl.pyc	537	9292cb548f29c21f4b0125656d34f0f0
+library/_ssl.pyc	537	474cda3f8885b7c4f1c665c43e7d4f5b
 library/os2emxpath.pyc	4312	0c35794eeb4739ebb4eb28b9a1daf0ca
 library/pprint.pyc	9852	6b4724e14285c257116655b1d4ba6db1
@@ -428,7 +433,7 @@
 library/encodings/cp1252.pyc	2676	b4c43cc72650c911cf3654dbaf565df9
 library/unicodedata.pyd	688128	c856c31a03dc892e4f2aee93d0bfee44
-library/unicodedata.pyc	558	dcdfa7fdce1179d8097934a44c983de1
+library/unicodedata.pyc	558	fcc10e2b903b6c28c0c187048a167372
 library/encodings/cp1256.pyc	2675	c1bf26ed9c9b5ee0eec495138993cffb
-doc/manual/en/index.html	47073	66a8cdd22cfd3d819c02f3aaab48eaea
+doc/manual/en/index.html	51535	4318d796e83fdc3187bcd2a2ebb36ec5
 library/email/base64mime.pyc	5221	c536d6f0c03f029bba77f15ef6ba3d99
 logo.png	69816	4a7517b22cfd75cc9c15e3fc1b7ebeec
@@ -452,5 +457,5 @@
 library/encodings/zlib_codec.pyc	4308	319c9073aff840436a03cc1fa0b08fb5
 locales/nl.pak	7068	939be31defbe6bd9f925331ef3987f9a
-doc/manual/hu/connect.png	28822	929f3eb85ee3cad019e12017c923f664
+doc/manual/hu/connect.png	31517	4cfdc3f3264248ca3cbf9b3dd14d5d7a
 library/encodings/palmos.pyc	2876	9284ad53dcfd82dfae0a79923b1cf91d
 library/mlx/web.pyc	36866	96511f975c55c000b0e9c16dff3aa31b
@@ -465,5 +470,5 @@
 library/distutils/log.pyc	2546	c50dfe4041313cc366461a899a1650cf
 locales/hr.pak	7430	a06f81a87b3fdb822a4f733abd21d1e9
-doc/manual/hu/finish.png	31044	adb40bacabf2cc597687fffe5c3340de
+doc/manual/hu/finish.png	33753	ef2043090093f8e2d9ccd40f4ee0604e
 locales/pl.pak	7227	14de2b7637dd2b260184c6ec85976091
 library/email/__init__.pyc	2752	5e1a1ba649ac697d631b031987d09c33
@@ -474,5 +479,5 @@
 locales/fa.pak	9886	475b94417d7b2af113fb147a0c45da0f
 locales/fr.pak	7917	bac6c149685b4b84617ca8c8c39306b7
-doc/manual/en/payload.png	28191	b9d22d6226541b8f58df89c7c9da9a62
+doc/manual/en/payload.png	29406	8d3996e5dc6e9511272ac635e53055b1
 library/selenium/webdriver/support/__init__.pyc	126	b99fdca3533d28cae3e7a63624932966
 library/sets.pyc	15775	ea560db2e34dae27d1fc9619a342ced8
@@ -481,7 +486,7 @@
 library/encodings/unicode_escape.pyc	1996	32990583f1caf874e556087da7b92971
 library/selenium/webdriver/support/expected_conditions.pyc	14298	43b8884d089d85e074d0d9f2fbcd5e28
-doc/manual/hu/logTab.png	28706	f48004202310bd2a1cb2f873d8064e93
-library/win32event.pyc	555	d66e08d9b35f124fdff12cf4f8273869
-library/win32file.pyc	552	6a3826b7caca276e9a6e1b84f20302f5
+doc/manual/hu/logTab.png	28908	25b9aceb760189c177a5562c0cfa2a04
+library/win32event.pyc	555	b5579d79e17fa66f9941be4e5253f42d
+library/win32file.pyc	552	4413a47389ea1f5ddc8b1c13a773a6bd
 library/encodings/shift_jis_2004.pyc	1710	f8bb22a8b02dba2b66b7f77122c98fd6
 library/encodings/cp856.pyc	2701	8b6649490089bedd87e9b8c56985f6b3
@@ -490,5 +495,5 @@
 library/encodings/cp850.pyc	7621	20d776af743fa7acf090fbd575eea143
 library/gio/__init__.pyc	809	bc9332556e93183a58be5c10cba18557
-doc/manual/hu/debugLogTab.png	34237	e4d91a024beec781966f7de91ffd196d
+doc/manual/hu/debugLogTab.png	32687	9280a72a7c4d4dc5d93a133410505402
 library/weakref.pyc	13028	5d1c14275f47e8cf6d40bc6877fde976
 library/mlx/gui/checklist.pyc	12279	555775aaed42b3d0687e1b0f501a9f61
@@ -507,5 +512,5 @@
 library/StringIO.pyc	11100	f4eb4a2bb3d6fb0af925681c5375c673
 library/gtk/__init__.pyc	3025	9f6605bf84c6cbd3c20f1d37e58af969
-doc/manual/en/helpTab.png	26243	cd5c021a2fc40485805b9fde03bcd060
+doc/manual/en/helpTab.png	27664	824b43ba072beae7a13954ece44c3492
 library/encodings/cp1257.pyc	2683	d24f94b6abbda684c0ad9f3370d86956
 library/selenium/webdriver/ie/service.pyc	3392	1f0c97b93092c2a239d70a3de49f0642
@@ -518,5 +523,5 @@
 library/mlx/gui/update.pyc	11427	5698abbc9b133f18fa2c37d3bfa74bee
 library/encodings/cp1250.pyc	2676	c92e287a2aa6a1c924df287d826aa3b7
-doc/manual/en/gatesTab.png	29585	00ebc96a2b4a7473dbb6a3162a04192f
+doc/manual/en/gatesTab.png	31516	7078e7c5adff9ee39816d2c3855d8523
 library/libcairo-2.dll	1294335	c0da2a3ab704f338f0fc6df51fd99497
 library/keyword.pyc	2063	4d5bab044f614cfdabe89011cd3d7375
@@ -529,5 +534,5 @@
 library/encodings/cp863.pyc	7868	fc7ff06dab536b93c68fcb8e7bac713f
 library/codecs.pyc	34722	339dc90c17d548887dbcf1bbbcae5d5b
-doc/manual/hu/gateSelection.png	29237	bac37e7001261a40a84e4202247f6052
+doc/manual/hu/gateSelection.png	31891	937dc56457188df05056c0f9f3a53975
 library/cmd.pyc	13512	808f813d5e354b7073a1356e44c7b554
 locales/ko.pak	7278	41ecc83d5eef5887c7c288b3b570c7b4
@@ -536,5 +541,5 @@
 locales/ta.pak	16595	5136010addd237205a5560f92b635589
 library/email/feedparser.pyc	10682	4de6a63da8dc270c9881edb9c2f28508
-doc/manual/en/route.png	25228	c1b2fb99d21cd862ea292d693400c955
+doc/manual/en/route.png	26389	9c3218e93a2883158bd9134f4d9513e1
 library/freetype6.dll	538324	fe5ec4b2a07d2c20a4cd3aa09ce0c571
 library/encodings/cp1140.pyc	2639	8320825626639ecf4604aac43477b65c
@@ -550,10 +555,10 @@
 library/_socket.pyd	38400	5d273be76603399e88cf57f2605004bf
 library/plistlib.pyc	18067	d8d36573e80bfdde7c73e47828f6e710
-library/_socket.pyc	546	fb1681f80f0e722983f70a325c23b3b5
+library/_socket.pyc	546	fa9a6c500373c472545594a194353a64
 library/encodings/hex_codec.pyc	3520	a8b8d5b7c8810379c7007bcbc253951c
 library/nturl2path.pyc	1755	7835082f2dd6806f99cc25bed13ff7dc
 library/mlx/sound.pyc	8226	03b098ed1dd965db98ef4f75b07d6cae
-library/glib/_glib.pyc	555	ab1cf44ec256636cfd820e5bbdb5cf14
-library/win32api.pyc	549	5753c8241e91acd8cb4b02cfc18719a3
+library/glib/_glib.pyc	555	b6480616e76444787a0a2378efc2cc43
+library/win32api.pyc	549	aa6d1f5583a8d058d9f80fff423be8c1
 library/encodings/iso8859_13.pyc	2696	f901ae0bbcfbb78f0c1783e89b2e2dc5
 library/selenium/webdriver/remote/remote_connection.pyc	21225	287db6d396dcce7180eada87e8c6ace0
@@ -564,5 +569,5 @@
 doc/manual/hu/prefsMessages.png	22980	6e60ec51305e95e5d057c0dd82b69460
 library/xml/parsers/__init__.pyc	294	83f6a77ed8cd962b3c75c4779dc557ac
-locale/en/LC_MESSAGES/mlx.mo	68019	e360bc22970f91fa071eec517a0a173c
+locale/en/LC_MESSAGES/mlx.mo	68078	9f93d7a348d26dd14c39706dc364750d
 library/_abcoll.pyc	19891	a5c1881934f1533c22b22b9317ac1d68
 library/encodings/iso8859_11.pyc	2787	a31e6d0d34e35259d7183cc9437eacca
@@ -571,10 +576,10 @@
 locales/da.pak	6631	1bf496bcde38f3f2899cd0fab6986959
 library/encodings/mac_latin2.pyc	4717	564dbaf5c296a147203eb821dc2466ba
-library/mlx/__init__.pyc	103	029f80bd80f880e6ca70ef4ba46f7d06
+library/mlx/__init__.pyc	103	877b8715b7e58440a0e8f212cab6035e
 library/mlx/i18n.pyc	1972	fe45816e49cf5e380bc39942a9dfa875
 library/xml/sax/saxutils.pyc	12382	32ed045b3b520234ebea6a508a6569fe
 locales/sw.pak	7078	5406f21a67934bbf0e6c633d2ca573f9
 library/encodings/mac_turkish.pyc	2739	1682e228ddfd2e74e3ccaee0b65e7261
-doc/manual/hu/gatesTab.png	30710	a6a59e6a2660c15418489e8785e4bcee
+doc/manual/hu/gatesTab.png	32840	316fb040e358c79b2a846b1eed4651c6
 library/webbrowser.pyc	18138	c85009edebad6d278fe786fec10ebbbe
 library/mlx/pyuipc_sim.pyc	57203	e1828998919f435bae2a5cbd969ff8f3
@@ -592,7 +597,7 @@
 locales/sk.pak	7622	dfdff6206e5a79aef8b46d0d9c2a8367
 library/select.pyd	11776	d90bf525f9f6c9c3aa6532fa6e569b4f
-library/pyuipc.pyc	543	906cca78dfad2ae877c0a2e1595d3d61
+library/pyuipc.pyc	543	73ee245cefd06c6ed52edf422c1bc6b1
 library/selenium/common/exceptions.pyc	9313	2140bc8b29eb7c3f4fc29622d0060152
-doc/manual/en/logTab.png	28522	305732f8485a15b7c802801038d688b7
+doc/manual/en/logTab.png	28120	b92cf63420e59e0c48da4f0b3baae040
 library/pyuipc.pyd	47616	2b4fe2d52575de71dda6f72690cf581e
 library/encodings/johab.pyc	1638	5346c11a863ffbcfb21aba8f1d2236b7
@@ -600,8 +605,8 @@
 library/encodings/quopri_codec.pyc	3365	01342d4d2735bcbfeb333d784297547a
 library/distutils/errors.pyc	5885	fbeaddf5d59ea781ec63197dd950aa89
-library/mlx/gui/mava_simbrief.pyc	10689	e91c026bd70fdde0432196a536017b7b
+library/mlx/gui/mava_simbrief.pyc	10689	f072e3f19b33a1aa5200028873c4986c
 library/logging/__init__.pyc	54813	7bcdf2e8da474f5e94d8e499f6fc3ed4
 library/selenium/webdriver/phantomjs/service.pyc	3263	3a56577bb9f058e0d7e5be544a8e850b
-runmlx.exe	63488	3060aa0ed73648edc26992e224568352
+runmlx.exe	64000	379d0cb552e1b8fd208b8f6f5da8f4ba
 locales/zh-CN.pak	6386	36e1df83611aef6fbfeeafd067dd4241
 locales/hi.pak	13321	4b6b27ab4421c94685fc09051a2339f8
@@ -612,4 +617,5 @@
 library/glib/option.pyc	11891	bd90183c75d8f473ecf653e3d63a27f9
 library/encodings/ascii.pyc	2081	a5fffd04734740386629fdba1113d585
+library/selenium/webdriver/firefox/__init__.pyc	126	0ce57f6223e88783f1a5d4d8c4998682
 Microsoft.VC90.CRT/msvcp90.dll	568832	6de5c66e434a9c1729575763d891c6c2
 library/sre_parse.pyc	18380	fe2376a101668be1124f9ee316662bab
Index: locale/en/mlx.po
===================================================================
--- locale/en/mlx.po	(revision 729)
+++ locale/en/mlx.po	(revision 764)
@@ -94,4 +94,7 @@
 msgstr "_Send..."
 
+msgid "button_register"
+msgstr "_Register"
+
 msgid "button_cancelFlight"
 msgstr "Cancel flight"
@@ -233,4 +236,12 @@
 msgstr "_Reconnect"
 
+msgid "login_title"
+msgstr "Login"
+
+msgid "login_info"
+msgstr ""
+"Enter your MAVA pilot's ID and password to\n"
+"log in to the MAVA website."
+
 msgid "login"
 msgstr "Login"
@@ -261,5 +272,5 @@
 msgid "login_remember_tooltip"
 msgstr ""
-"If checked, your password will be stored, so that you "
+"If checked, your password will be stored by the program, so that you "
 "should not have to enter it every time. Note, however, that "
 "the password is stored as text, and anybody who can access "
@@ -271,4 +282,10 @@
 msgid "login_entranceExam_tooltip"
 msgstr "Check this to log in to take your entrance exam."
+
+msgid "button_login_register"
+msgstr "R_egister..."
+
+msgid "button_login_register_tooltip"
+msgstr "Click here if you would like to register with Malév Virtual"
 
 msgid "button_offline"
@@ -1737,4 +1754,14 @@
 "know what you are doing!"
 
+msgid "prefs_use_rpc"
+msgstr "Use _RPC calls"
+
+msgid "prefs_use_rpc_tooltip"
+msgstr ""
+"If checked the program will use RPC calls towards the MAVA server. "
+"This should be the normal operation, but if some server operation "
+"fails, you may want to try to uncheck here. Note, that some new "
+"functionality will still be called over RPC."
+
 msgid "prefs_msgs_fs"
 msgstr "Displayed in FS"
@@ -2095,2 +2122,191 @@
 msgid "sendBugReport_error_sec"
 msgstr "There was some communication error, try again later."
+
+msgid "register_ask"
+msgstr "Would you like to register with MAVA?"
+
+msgid "register_ask_sec"
+msgstr ""
+"It seems, you have not run the program\n"
+"on this computer, or at least not logged in yet.\n"
+"\n"
+"If you would like to register, click the\n"
+"<b>Register</b> button below and fill the form."
+
+msgid "register_title"
+msgstr "Registration"
+
+msgid "register_help"
+msgstr ""
+"Enter the data below.\n"
+"Optional data items are in <i>italics</i>.\n"
+"One of VATSIM ID or IVAO ID is mandatory."
+
+msgid "register_name1"
+msgstr "First _name:"
+
+msgid "register_name1_tooltip"
+msgstr "Enter your real first name."
+
+msgid "register_name2"
+msgstr "Sur_name:"
+
+msgid "register_name2_tooltip"
+msgstr "Enter your real surname."
+
+msgid "register_nameorder"
+msgstr "western"
+
+msgid "register_year_of_birth"
+msgstr "_Year of birth:"
+
+msgid "register_year_of_birth_tooltip"
+msgstr "Enter the year in which you were born."
+
+msgid "register_email"
+msgstr "_E-mail:"
+
+msgid "register_email_tooltip"
+msgstr "Enter your e-mail address."
+
+msgid "register_email_public"
+msgstr "P_ublic"
+
+msgid "register_email_public_tooltip"
+msgstr ""
+"If checked, your e-mail address will be displayed in our forums and "
+"possibly other internal website areas, and thus it will be made "
+"available to other members of MAVA."
+
+msgid "register_vatsim_id"
+msgstr "_VATSIM CID:"
+
+msgid "register_vatsim_id_tooltip"
+msgstr ""
+"Enter your VATSIM CID, if you are registered with VATSIM. "
+"Note that either a VATSIM CID or an IVAO VID is required for the registration."
+
+msgid "register_ivao_id"
+msgstr "IV_AO VID:"
+
+msgid "register_ivao_id_tooltip"
+msgstr ""
+"Enter your IVAO VID, if you are registered with IVAO. "
+"Note that either a VATSIM CID or an IVAO VID is required for the registration."
+
+msgid "register_phone_num"
+msgstr "<i>Phone num_ber</i>:"
+
+msgid "register_phone_num_tooltip"
+msgstr "Enter your phone number, if we can reach you by phone."
+
+msgid "register_nationality"
+msgstr "<i>Na_tionality</i>:"
+
+msgid "register_nationality_tooltip"
+msgstr "Enter your nationality, if you would like to share that info with us."
+
+msgid "register_password"
+msgstr "Pa_ssword:"
+
+msgid "register_password_tooltip"
+msgstr "Enter the password you would like to use for logging in."
+
+msgid "register_password2"
+msgstr "Pass_word again:"
+
+msgid "register_password2_tooltip"
+msgstr "Enter the same password again."
+
+msgid "register_password_ok"
+msgstr "<span foreground=\"darkgreen\"><b>OK</b></span>"
+
+msgid "register_password_too_short"
+msgstr "<span foreground=\"orange\"><b>Too short</b></span>"
+
+msgid "register_password_mismatch"
+msgstr "<span foreground=\"red\"><b>Passwords differ</b></span>"
+
+msgid "register_busy"
+msgstr "Registering..."
+
+msgid "register_ok"
+msgstr "Registration successful."
+
+msgid "register_failed"
+msgstr "Registration failed!"
+
+msgid "register_info"
+msgstr ""
+"Congratulations! You are now a MAVA pilot\n"
+"in status STU (student).\n"
+"\n"
+"Your pilot ID is: <b>%s</b>.\n"
+"\n"
+"You will now be taken to the student page,\n"
+"where you can initiate the taking of the\n"
+"entrance exam or start your check flight.\n"
+"\n"
+"You can return to the page anytime later\n"
+"by starting the program, and logging in."
+
+msgid "register_nologin"
+msgstr ""
+"Congratulations! You are now a MAVA pilot\n"
+"in status STU (student).\n"
+"\n"
+"Your pilot ID is: <b>%s</b>.\n"
+"\n"
+"For some reason, we could not log you in\n"
+"(perhaps a server failure).\n"
+"\n"
+"You will now be taken to the login page,\n"
+"where you can try logging in again. You can,\n"
+"of course, start the program at any time\n"
+"later on and attempt logging in."
+
+msgid "register_email_already"
+msgstr "The given e-mail is already registered."
+
+msgid "register_invalid_data"
+msgstr ""
+"The program has sent invalid data to the server.\n"
+"This may be a program error. You may try to alter some\n"
+"data (e.g. accented characters), or send a bug report."
+
+msgid "register_error"
+msgstr ""
+"Some error, most likely in the server, has occured.\n"
+"Please, try again later."
+
+msgid "register_result_title"
+msgstr "Registration"
+
+msgid "student_title"
+msgstr "Student"
+
+msgid "student_help"
+msgstr ""
+"You can initiate the entrace exam\n"
+"and the check flight here."
+
+msgid "student_entry_exam_status"
+msgstr "Entry exam:"
+
+msgid "student_entry_exam_passed"
+msgstr "<span color=\"darkgreen\"><b>Passed</b></span>"
+
+msgid "student_entry_exam_not_passed"
+msgstr "<span color=\"orange\"><b>Not yet passed</b></span>"
+
+msgid "student_entry_exam"
+msgstr "Take the _entry exam"
+
+msgid "student_entry_exam_tooltip"
+msgstr ""
+"Click this button to go to MAVA's training website in your browser and take "
+"the theoretical entry exam. The website might ask you for login: you can "
+"use your MAVA pilot ID (PXXX) and password. "
+"If this button is greyed out, it means that you have already passed "
+"the exam earlier. Note, that its status is updated only a few minutes "
+"after passing the exam."
Index: locale/hu/mlx.po
===================================================================
--- locale/hu/mlx.po	(revision 729)
+++ locale/hu/mlx.po	(revision 764)
@@ -94,4 +94,7 @@
 msgstr "_Küldés..."
 
+msgid "button_register"
+msgstr "_Regisztráció"
+
 msgid "button_cancelFlight"
 msgstr "Járat megszakítása"
@@ -233,4 +236,12 @@
 msgstr "Újra_kapcsolódás"
 
+msgid "login_title"
+msgstr "Bejelentkezés"
+
+msgid "login_info"
+msgstr ""
+"Írd be a MAVA pilóta azonosítódat és a\n"
+"bejelentkezéshez használt jelszavadat."
+
 msgid "login"
 msgstr "Bejelentkezés"
@@ -270,4 +281,10 @@
 msgid "login_entranceExam_tooltip"
 msgstr "Ha ezt bejelölöd, ellenörző repülésre jelentkezhetsz be."
+
+msgid "button_login_register"
+msgstr "_Regisztráció..."
+
+msgid "button_login_register_tooltip"
+msgstr "Kattints ide a Malév Virtualhoz való regisztrálás céljából."
 
 msgid "button_offline"
@@ -1748,4 +1765,14 @@
 "ha biztos vagy a dolgodban!"
 
+msgid "prefs_use_rpc"
+msgstr "_RPC hívások"
+
+msgid "prefs_use_rpc_tooltip"
+msgstr ""
+"Ha ez be van jelölve, a program RPC hívásokkal kommunikál a MAVA "
+"szerverrel. Ez a normális, azonban ha valamilyen szerverművelet "
+"nem sikerül, megpróbálhatjuk itt megszüntetni a kijelölést. "
+"Azonban bizonyos új funkciókat továbbra is RPC-n fog hívni a program."
+
 msgid "prefs_msgs_fs"
 msgstr ""
@@ -2109,2 +2136,193 @@
 msgid "sendBugReport_error_sec"
 msgstr "Valamilyen kommunikációs probléma adódott, próbáld újra később."
+
+msgid "register_ask"
+msgstr "Szeretnél regisztrálni?"
+
+msgid "register_ask_sec"
+msgstr ""
+"Úgy tűnik, nem futtattad még a programot ezen a\n"
+"számítógépen, vagy legalábbis nem jelentkeztél még be.\n"
+"\n"
+"Ha szeretnél regisztrálni, kattints a <b>Regisztráció</b>\n"
+"gombra, majd töltsd ki a megjelenő űrlapot."
+
+msgid "register_title"
+msgstr "Regisztráció"
+
+msgid "register_help"
+msgstr ""
+"Töltsd ki a lenti űrlapot.\n"
+"A <i>dőlt</i> betűs adatokat nem kötelező megadnod.\n"
+"Vagy VATSIM vagy IVAO azonosítót meg kell adni."
+
+msgid "register_name1"
+msgstr "_Vezetéknév:"
+
+msgid "register_name1_tooltip"
+msgstr "Írd be a valódi vezetéknevedet"
+
+msgid "register_name2"
+msgstr "Keresz_tnév:"
+
+msgid "register_name2_tooltip"
+msgstr "Írd be a valódi keresztnevedet"
+
+msgid "register_nameorder"
+msgstr "eastern"
+
+msgid "register_year_of_birth"
+msgstr "S_zületés éve:"
+
+msgid "register_year_of_birth_tooltip"
+msgstr "Írd be az évnek a számát, amelyben születtél.."
+
+msgid "register_email"
+msgstr "E-m_ail:"
+
+msgid "register_email_tooltip"
+msgstr "Írd be az e-mail címedet."
+
+msgid "register_email_public"
+msgstr "P_ublikus"
+
+msgid "register_email_public_tooltip"
+msgstr ""
+"Ha ezt bejelölöd, az e-mail címed megjelenhet a fórumunkban és "
+"egyéb belső weboldalakon is, így azt a MAVA többi tagja "
+"láthatja."
+
+msgid "register_vatsim_id"
+msgstr "VATSIM _CID:"
+
+msgid "register_vatsim_id_tooltip"
+msgstr ""
+"Ha rendelkezel VATSIM regisztrációval, írd be ide az azonosítódat. "
+"A regisztrációhoz vagy VATSIM vagy IVAO azonosító szükséges."
+
+msgid "register_ivao_id"
+msgstr "IVA_O VID:"
+
+msgid "register_ivao_id_tooltip"
+msgstr ""
+"Ha rendelkezel IVAO regisztrációval, írd be ide az azonosítódat. "
+"A regisztrációhoz vagy VATSIM vagy IVAO azonosító szükséges."
+
+msgid "register_phone_num"
+msgstr "<i>Tele_fonszám</i>:"
+
+msgid "register_phone_num_tooltip"
+msgstr "Ha telefonon is kereshetünk, írd be a számodat."
+
+msgid "register_nationality"
+msgstr "<i>Nemzetisé_g</i>:"
+
+msgid "register_nationality_tooltip"
+msgstr "Írd be a nemzetiségedet, ha meg kívánod azt velünk osztani."
+
+msgid "register_password"
+msgstr "Je_lszó:"
+
+msgid "register_password_tooltip"
+msgstr "Írd be a jelszót, amivel be szeretnél majd jelentkezni."
+
+msgid "register_password2"
+msgstr "Jelszó új_ra:"
+
+msgid "register_password2_tooltip"
+msgstr "Írd be ugyanazt a jelszót megint."
+
+msgid "register_password_ok"
+msgstr "<span foreground=\"darkgreen\"><b>Rendben</b></span>"
+
+msgid "register_password_too_short"
+msgstr "<span foreground=\"orange\"><b>Túl rövid</b></span>"
+
+msgid "register_password_mismatch"
+msgstr "<span foreground=\"red\"><b>A két jelszó különbözik</b></span>"
+
+msgid "register_busy"
+msgstr "Regisztráció..."
+
+msgid "register_ok"
+msgstr "A regisztráció sikerült."
+
+msgid "register_failed"
+msgstr "A regisztráció nem sikerült!"
+
+msgid "register_info"
+msgstr ""
+"Gratulálunk! Ezennel a MAVA pilótája lettél\n"
+"STU (student - tanuló) státuszban.\n"
+"\n"
+"Pilóta azonosítód: <b>%s</b>.\n"
+"\n"
+"Most a tanulók oldala fog megjelenni,\n"
+"ahol kezdeményezheted a belépő teszt\n"
+"kitöltését vagy elindíthatod az ellenőrző\n"
+"repülést.\n"
+"\n"
+"A tanulók oldalára bármikor visszatérhetsz\n"
+"ha elindítod a programot és bejelentkezel."
+
+msgid "register_nologin"
+msgstr ""
+"Gratulálunk! Ezennel a MAVA pilótája lettél\n"
+"STU (student - tanuló) státuszban.\n"
+"\n"
+"Pilóta azonosítód: <b>%s</b>.\n"
+"\n"
+"Valamilyen oknál fogva a bejelentkezés viszont\n"
+"nem sikerült (talán szerverhiba).\n"
+"\n"
+"A bejelentkező oldal fog megjelenni,\n"
+"ahol megpróbálhatsz belépni. Természetesen\n"
+"a programot bármikor futtathatod és\n"
+"megpróbálhatod a belépést."
+
+msgid "register_email_already"
+msgstr "A megadott e-mail címmel már regisztráltak."
+
+msgid "register_invalid_data"
+msgstr ""
+"A program érvényelen adatokat küldött a szervernek.\n"
+"Ez programhiba lehet. Megpróbálhatsz megváltoztatni\n"
+"bizonyos adatokat (pl. az ékezetes karaktereket),\n"
+"vagy küldhetsz hibajelentést."
+
+msgid "register_error"
+msgstr ""
+"Valamilyen, valószínűleg szerverhiba történt.\n"
+"Kérlek, próbáld meg később."
+
+msgid "register_result_title"
+msgstr "Regisztráció"
+
+msgid "student_title"
+msgstr "Tanuló"
+
+msgid "student_help"
+msgstr ""
+"Itt intézheted a felvételi elméleti vizsgát\n"
+"és az ellenőrző repülést."
+
+msgid "student_entry_exam_status"
+msgstr "Elméleti vizsga:"
+
+msgid "student_entry_exam_passed"
+msgstr "<span color=\"darkgreen\"><b>Sikeres</b></span>"
+
+msgid "student_entry_exam_not_passed"
+msgstr "<span color=\"orange\"><b>Még nem tetted le</b></span>"
+
+msgid "student_entry_exam"
+msgstr "_Elméleti vizsga"
+
+msgid "student_entry_exam_tooltip"
+msgstr ""
+"Erre a gombra kattintva a MAVA oktatási weboldalára kerülsz "
+"böngésződben és kitöltheted az elméleti vizsga tesztjét. "
+"A weboldalon szükség lehet bejelentkezned: ebben az esetben "
+"használd a MAVA pilóta azonosítódat (PXXX) és jelszavadat. "
+"Ha a gomb inaktív, a vizsgát korábban már sikeresen teljesítetted. "
+"A gomb állapota csak néhány perccel a vizsga teljesítése után frissül."
Index: src/mlx/acars.py
===================================================================
--- src/mlx/acars.py	(revision 298)
+++ src/mlx/acars.py	(revision 743)
@@ -25,5 +25,5 @@
         self.state = state
         self.bookedFlight = gui.bookedFlight
-        
+
         self.blockTime = 0 if flight.blockTimeStart is None \
                          else aircraft.state.timestamp - flight.blockTimeStart
@@ -48,4 +48,26 @@
         else:
             return "Landed"
-        
+
+    def _serialize(self):
+        """Serialize the ACARS object for JSON-RPC."""
+        bookedFlight = self.bookedFlight
+        state = self.state
+
+        attrs = {}
+        attrs["longitude"] = state.longitude
+        attrs["latitude"] = state.latitude
+        attrs["pilotName"] = self.pilotName
+        attrs["numPassengers"] = bookedFlight.numPassengers
+        attrs["blockTime"] = self.getBlockTimeText()
+        attrs["callsign"] = bookedFlight.callsign
+        attrs["aircraftTypeName"] = bookedFlight.aircraftTypeName
+        attrs["departureICAO"] = bookedFlight.departureICAO
+        attrs["arrivalICAO"] = bookedFlight.arrivalICAO
+        attrs["altitude"] = state.altitude
+        attrs["groundSpeed"] = state.groundSpeed
+        attrs["event"] = self.getEventText()
+        attrs["tailNumber"] = bookedFlight.tailNumber
+
+        return ([], attrs)
+
 #------------------------------------------------------------------------------
Index: src/mlx/common.py
===================================================================
--- src/mlx/common.py	(revision 651)
+++ src/mlx/common.py	(revision 740)
@@ -15,4 +15,8 @@
 # used, which is the default on Windows or when the \c FORCE_PYGTK environment
 # variable is set.
+
+#---------------------------------------------------------------------------------------
+
+MAVA_BASE_URL = os.environ.get("MAVA_BASE_URL", "http://virtualairlines.hu")
 
 #-------------------------------------------------------------------------------
Index: src/mlx/config.py
===================================================================
--- src/mlx/config.py	(revision 733)
+++ src/mlx/config.py	(revision 738)
@@ -258,4 +258,5 @@
         if secondaryInstallation:
             self._updateURL += "/exp"
+        self._useRPC = True
 
         self._messageTypeLevels = {}
@@ -666,4 +667,16 @@
         if updateURL!=self._updateURL:
             self._updateURL = updateURL
+            self._modified = True
+
+    @property
+    def useRPC(self):
+        """Determine if RPC calls should be used."""
+        return self._useRPC
+
+    @useRPC.setter
+    def useRPC(self, useRPC):
+        """Set whether RPC calls should be used."""
+        if useRPC!=self._useRPC:
+            self._useRPC = useRPC
             self._modified = True
 
@@ -775,4 +788,5 @@
                                     Config.DEFAULT_UPDATE_URL +
                                     ("/exp" if secondaryInstallation else ""))
+        self._useRPC = self._getBoolean(config, "general", "useRPC", True)
 
         for aircraftType in const.aircraftTypes:
@@ -867,4 +881,7 @@
         config.set("update", "url", self._updateURL)
 
+        config.set("general", "useRPC",
+                   "yes" if self._useRPC else "no")
+
         config.add_section(Checklist.SECTION)
         config.add_section(ApproachCallouts.SECTION)
@@ -1001,4 +1018,5 @@
         print "  autoUpdate:", self._autoUpdate
         print "  updateURL:", self._updateURL
+        print "  useRPC:", self._useRPC
 
         print "  messageTypeLevels:"
Index: src/mlx/const.py
===================================================================
--- src/mlx/const.py	(revision 750)
+++ src/mlx/const.py	(revision 737)
@@ -11,5 +11,5 @@
 
 ## The version of the program
-VERSION="0.37.1"
+VERSION="0.38"
 
 #-------------------------------------------------------------------------------
Index: src/mlx/fsuipc.py
===================================================================
--- src/mlx/fsuipc.py	(revision 765)
+++ src/mlx/fsuipc.py	(revision 766)
@@ -1387,7 +1387,4 @@
         state.gearsDown = data[self._monidx_noseGear]==16383
 
-        if state.gearControlDown!=state.gearsDown:
-            print "gearControl:", data[self._monidx_gearControl], ", noseGear:", data[self._monidx_noseGear]
-
         state.spoilersArmed = data[self._monidx_spoilersArmed]!=0
 
Index: src/mlx/gui/acars.py
===================================================================
--- src/mlx/gui/acars.py	(revision 649)
+++ src/mlx/gui/acars.py	(revision 747)
@@ -13,5 +13,5 @@
 # The ACARS tab.
 #
-# This module implements to \ref ACRS class, which displays the MAVA ACARS in a
+# This module implements to \ref ACARS class, which displays the MAVA ACARS in a
 # browser window using CEF
 
@@ -21,5 +21,5 @@
     """The flight info tab."""
     # The URL of the ACARS map
-    URL = "http://virtualairlines.hu/acars2/show.html"
+    URL = MAVA_BASE_URL + "/acars2/show.html"
 
     def __init__(self, gui):
Index: src/mlx/gui/common.py
===================================================================
--- src/mlx/gui/common.py	(revision 732)
+++ src/mlx/gui/common.py	(revision 741)
@@ -279,4 +279,119 @@
                       else str(self._currentInteger))
         self._selfSetting = False
+
+#------------------------------------------------------------------------------
+
+class CredentialsDialog(gtk.Dialog):
+    """A dialog window to ask for a user name and a password."""
+    def __init__(self, gui, userName, password,
+                 titleLabel, cancelButtonLabel, okButtonLabel,
+                 userNameLabel, userNameTooltip,
+                 passwordLabel, passwordTooltip,
+                 infoText = None,
+                 rememberPassword = None,
+                 rememberLabel = None, rememberTooltip = None):
+        """Construct the dialog."""
+        super(CredentialsDialog, self).__init__(WINDOW_TITLE_BASE + " - " +
+                                                titleLabel,
+                                                gui.mainWindow,
+                                                DIALOG_MODAL)
+        self.add_button(cancelButtonLabel, RESPONSETYPE_CANCEL)
+        self.add_button(okButtonLabel, RESPONSETYPE_OK)
+
+        contentArea = self.get_content_area()
+
+        contentAlignment = gtk.Alignment(xalign = 0.5, yalign = 0.5,
+                                         xscale = 0.0, yscale = 0.0)
+        contentAlignment.set_padding(padding_top = 4, padding_bottom = 16,
+                                     padding_left = 8, padding_right = 8)
+
+        contentArea.pack_start(contentAlignment, False, False, 0)
+
+        contentVBox = gtk.VBox()
+        contentAlignment.add(contentVBox)
+
+        if infoText is not None:
+            label = gtk.Label(infoText)
+            label.set_alignment(0.0, 0.0)
+
+            contentVBox.pack_start(label, False, False, 0)
+
+        tableAlignment = gtk.Alignment(xalign = 0.5, yalign = 0.5,
+                                       xscale = 0.0, yscale = 0.0)
+        tableAlignment.set_padding(padding_top = 24, padding_bottom = 0,
+                                   padding_left = 0, padding_right = 0)
+
+        table = gtk.Table(3, 2)
+        table.set_row_spacings(4)
+        table.set_col_spacings(16)
+        table.set_homogeneous(False)
+
+        tableAlignment.add(table)
+        contentVBox.pack_start(tableAlignment, True, True, 0)
+
+        label = gtk.Label(userNameLabel)
+        label.set_use_underline(True)
+        label.set_alignment(0.0, 0.5)
+        table.attach(label, 0, 1, 0, 1)
+
+        self._userName = gtk.Entry()
+        self._userName.set_width_chars(16)
+        # FIXME: enabled the OK button only when there is something in thr
+        # user name and password fields
+        #self._userName.connect("changed",
+        #                       lambda button: self._updateForwardButton())
+        self._userName.set_tooltip_text(userNameTooltip)
+        self._userName.set_text(userName)
+        table.attach(self._userName, 1, 2, 0, 1)
+        label.set_mnemonic_widget(self._userName)
+
+        label = gtk.Label(passwordLabel)
+        label.set_use_underline(True)
+        label.set_alignment(0.0, 0.5)
+        table.attach(label, 0, 1, 1, 2)
+
+        self._password = gtk.Entry()
+        self._password.set_visibility(False)
+        #self._password.connect("changed",
+        #                       lambda button: self._updateForwardButton())
+        self._password.set_tooltip_text(passwordTooltip)
+        self._password.set_text(password)
+        table.attach(self._password, 1, 2, 1, 2)
+        label.set_mnemonic_widget(self._password)
+
+        if rememberPassword is not None:
+            self._rememberButton = gtk.CheckButton(rememberLabel)
+            self._rememberButton.set_use_underline(True)
+            self._rememberButton.set_tooltip_text(rememberTooltip)
+            self._rememberButton.set_active(rememberPassword)
+            table.attach(self._rememberButton, 1, 2, 2, 3, ypadding = 8)
+        else:
+            self._rememberButton = None
+
+    @property
+    def userName(self):
+        """Get the user name entered."""
+        return self._userName.get_text()
+
+    @property
+    def password(self):
+        """Get the password entered."""
+        return self._password.get_text()
+
+    @property
+    def rememberPassword(self):
+        """Get whether the password is to be remembered."""
+        return None if self._rememberButton is None \
+               else self._rememberButton.get_active()
+
+    def run(self):
+        """Run the dialog."""
+        self.show_all()
+
+        response = super(CredentialsDialog, self).run()
+
+        self.hide()
+
+        return response
 
 #------------------------------------------------------------------------------
Index: src/mlx/gui/flight.py
===================================================================
--- src/mlx/gui/flight.py	(revision 728)
+++ src/mlx/gui/flight.py	(revision 764)
@@ -11,5 +11,5 @@
 import mlx.util as util
 from mlx.pirep import PIREP
-from mlx.i18n import xstr
+from mlx.i18n import xstr, getLanguage
 from mlx.sound import startSound
 import mlx.web as web
@@ -20,4 +20,6 @@
 import tempfile
 import threading
+import re
+import webbrowser
 
 #-----------------------------------------------------------------------------
@@ -61,5 +63,5 @@
 class Page(gtk.Alignment):
     """A page in the flight wizard."""
-    def __init__(self, wizard, title, help, completedHelp = None):
+    def __init__(self, wizard, id, title, help, completedHelp = None):
         """Construct the page."""
         super(Page, self).__init__(xalign = 0.0, yalign = 0.0,
@@ -139,4 +141,6 @@
 
         self._wizard = wizard
+        self._id = id
+        self._nextPageID = None
 
         self._cancelFlightButton = None
@@ -144,4 +148,19 @@
         self._completed = False
         self._fromPage = None
+
+    @property
+    def id(self):
+        """Get the identifier of the page."""
+        return self._id
+
+    @property
+    def nextPageID(self):
+        """Get the identifier of the next page, if set."""
+        return self._nextPageID
+
+    @nextPageID.setter
+    def nextPageID(self, nextPageID):
+        """Set the identifier of the next page."""
+        self._nextPageID = nextPageID
 
     def setMainWidget(self, widget):
@@ -277,11 +296,11 @@
     def __init__(self, wizard):
         """Construct the login page."""
-        super(LoginPage, self).__init__(wizard, xstr("login"),
-                                        xstr("loginHelp"))
+        super(LoginPage, self).__init__(wizard, "login",
+                                        xstr("login"), xstr("loginHelp"))
 
         alignment = gtk.Alignment(xalign = 0.5, yalign = 0.5,
                                   xscale = 0.0, yscale = 0.0)
 
-        table = gtk.Table(4, 2)
+        table = gtk.Table(3, 2)
         table.set_row_spacings(4)
         table.set_col_spacings(32)
@@ -321,9 +340,7 @@
         table.attach(self._rememberButton, 1, 2, 2, 3, ypadding = 8)
 
-        self._entranceExam = gtk.CheckButton(xstr("login_entranceExam"))
-        self._entranceExam.set_use_underline(True)
-        self._entranceExam.set_tooltip_text(xstr("login_entranceExam_tooltip"))
-        self._entranceExam.connect("toggled", self._setControls)
-        table.attach(self._entranceExam, 1, 2, 3, 4, ypadding = 12)
+        self.addButton(xstr("button_login_register"),
+                       clicked = self._registerClicked,
+                       tooltip = xstr("button_login_register_tooltip"))
 
         self.addButton(xstr("button_offline"),
@@ -335,10 +352,4 @@
         self._loginButton.set_tooltip_text(xstr("login_button_tooltip"))
 
-
-    @property
-    def entranceExam(self):
-        """Get whether an entrance exam is being performed."""
-        return self._entranceExam.get_active() and \
-               self._pilotID.get_text()!=""
 
     @property
@@ -379,10 +390,10 @@
         pilotID = self._pilotID.get_text()
         password = self._password.get_text()
-        entranceExam = self._entranceExam.get_active()
-        self._password.set_sensitive(not entranceExam)
-        self._rememberButton.set_sensitive(password!="" and not entranceExam)
-        self._entranceExam.set_sensitive(pilotID!="")
-        self._loginButton.set_sensitive(pilotID!="" and
-                                        (password!="" or entranceExam))
+        self._rememberButton.set_sensitive(password!="")
+        self._loginButton.set_sensitive(pilotID!="" and password!="")
+
+    def _registerClicked(self, button):
+        """Called when the Register button was clicked."""
+        self._wizard.jumpPage("register")
 
     def _offlineClicked(self, button):
@@ -396,6 +407,5 @@
         self._wizard.login(self._handleLoginResult,
                            self._pilotID.get_text(),
-                           self._password.get_text(),
-                           self.entranceExam)
+                           self._password.get_text())
 
     def _handleLoginResult(self, returned, result):
@@ -413,5 +423,8 @@
 
             config.save()
-            self._wizard.nextPage()
+            if result.rank=="STU":
+                self._wizard.jumpPage("student")
+            else:
+                self._wizard.nextPage()
 
 #-----------------------------------------------------------------------------
@@ -423,5 +436,6 @@
         help = xstr("flightsel_help")
         completedHelp = xstr("flightsel_chelp")
-        super(FlightSelectionPage, self).__init__(wizard, xstr("flightsel_title"),
+        super(FlightSelectionPage, self).__init__(wizard, "flightsel",
+                                                  xstr("flightsel_title"),
                                                   help, completedHelp = completedHelp)
 
@@ -603,5 +617,5 @@
         """Called when the forward button was clicked."""
         if self._completed:
-            self._wizard.jumpPage(self._nextDistance, finalize = False)
+            self._wizard.jumpPage(self._nextID, finalize = False)
         else:
             self._flightSelected()
@@ -658,30 +672,30 @@
                                          const.PLANE_AWAY)
         else:
-            self._nextDistance = 2
-            self._wizard.jumpPage(2)
+            self._nextID = "connect"
+            self._wizard.jumpPage("connect")
 
     def _fleetRetrieved(self, fleet):
         """Called when the fleet has been retrieved."""
         if fleet is None:
-            self._nextDistance = 2
-            self._wizard.jumpPage(2)
+            self._nextID = "connect"
+            self._wizard.jumpPage("connect")
         else:
             plane = fleet[self._wizard._bookedFlight.tailNumber]
             if plane is None:
-                self._nextDistance = 2
-                self._wizard.jumpPage(2)
+                self._nextID = "connect"
+                self._wizard.jumpPage("connect")
             elif plane.gateNumber is not None and \
                  not fleet.isGateConflicting(plane):
                 self._wizard._departureGate = plane.gateNumber
-                self._nextDistance = 2
-                self._wizard.jumpPage(2)
+                self._nextID = "connect"
+                self._wizard.jumpPage("connect")
             else:
-                self._nextDistance = 1
-                self._wizard.nextPage()
+                self._nextID = "gatesel"
+                self._wizard.jumpPage("gatesel")
 
     def _planeUpdated(self, success):
         """Callback for the plane updating."""
-        self._nextDistance = 2
-        self._wizard.jumpPage(2)
+        self._nextID = "connect"
+        self._wizard.jumpPage("connect")
 
     def _getSaveDialog(self):
@@ -786,5 +800,6 @@
     def __init__(self, wizard):
         """Construct the gate selection page."""
-        super(GateSelectionPage, self).__init__(wizard, xstr("gatesel_title"),
+        super(GateSelectionPage, self).__init__(wizard, "gatesel",
+                                                xstr("gatesel_title"),
                                                 xstr("gatesel_help"))
 
@@ -849,5 +864,5 @@
             self._gateSelected()
         else:
-            self._wizard.nextPage()
+            self._wizard.jumpPage("connect")
 
     def _rowActivated(self, flightList, path, column):
@@ -871,5 +886,5 @@
         """Callback for the plane updating call."""
         if success is None or success:
-            self._wizard.nextPage()
+            self._wizard.jumpPage("connect")
         else:
             dialog = gtk.MessageDialog(parent = self._wizard.gui.mainWindow,
@@ -887,7 +902,554 @@
         """Called when the fleet has been retrieved."""
         if fleet is None:
-            self._wizard.nextPage()
+            self._wizard.jumpPage("connect")
         else:
             self.activate()
+
+#-----------------------------------------------------------------------------
+
+class RegisterPage(Page):
+    """A page to enter the registration data."""
+
+    # The minimal year of birth
+    _minYearOfBirth = 1900
+
+    # The maximal year of birth
+    _maxYearOfBirth = datetime.date.today().year
+
+    # The regular expression to check the e-mail address with
+    _emailAddressRE = re.compile("[^@]+@[^@]+\.[^@]+")
+
+    def __init__(self, wizard):
+        """Construct the registration page."""
+        super(RegisterPage, self).__init__(wizard, "register",
+                                           xstr("register_title"),
+                                           xstr("register_help"))
+
+        alignment = gtk.Alignment(xalign = 0.5, yalign = 0.5,
+                                  xscale = 0.0, yscale = 0.0)
+
+        table = gtk.Table(12, 3)
+        table.set_row_spacings(4)
+        table.set_col_spacings(32)
+        alignment.add(table)
+        self.setMainWidget(alignment)
+
+        row = 0
+
+        labelAlignment = gtk.Alignment(xalign = 1.0, yalign = 0.5,
+                                       xscale = 0.0, yscale = 0.0)
+        label = gtk.Label(xstr("register_name1"))
+        label.set_use_underline(True)
+        labelAlignment.add(label)
+        table.attach(labelAlignment, 0, 1, row, row+1)
+
+        self._name1 = gtk.Entry()
+        self._name1.connect("changed", self._updateButtons)
+        self._name1.set_tooltip_text(xstr("register_name1_tooltip"))
+        self._name1.set_width_chars(15)
+        table.attach(self._name1, 1, 2, row, row+1)
+        label.set_mnemonic_widget(self._name1)
+
+        row += 1
+
+        labelAlignment = gtk.Alignment(xalign = 1.0, yalign = 0.5,
+                                       xscale = 0.0, yscale = 0.0)
+        label = gtk.Label(xstr("register_name2"))
+        label.set_use_underline(True)
+        labelAlignment.add(label)
+        table.attach(labelAlignment, 0, 1, row, row+1)
+
+        self._name2 = gtk.Entry()
+        self._name2.connect("changed", self._updateButtons)
+        self._name2.set_tooltip_text(xstr("register_name2_tooltip"))
+        self._name2.set_width_chars(15)
+        table.attach(self._name2, 1, 2, row, row+1)
+        label.set_mnemonic_widget(self._name2)
+
+        row += 1
+
+        labelAlignment = gtk.Alignment(xalign = 1.0, yalign = 0.5,
+                                       xscale = 0.0, yscale = 0.0)
+        label = gtk.Label(xstr("register_year_of_birth"))
+        label.set_use_underline(True)
+        labelAlignment.add(label)
+        table.attach(labelAlignment, 0, 1, row, row+1)
+
+        alignment = gtk.Alignment(xalign = 0.0, yalign = 0.0,
+                                  xscale = 0.0, yscale = 0.0)
+
+        self._yearOfBirth = gtk.SpinButton()
+        self._yearOfBirth.set_increments(step = 1, page = 10)
+        self._yearOfBirth.set_range(min = RegisterPage._minYearOfBirth,
+                                    max = RegisterPage._maxYearOfBirth)
+        self._yearOfBirth.set_numeric(True)
+        self._yearOfBirth.set_tooltip_text(xstr("register_year_of_birth_tooltip"))
+        self._yearOfBirth.set_width_chars(5)
+        self._yearOfBirth.connect("changed", self._updateButtons)
+        self._yearOfBirth.connect("value-changed", self._updateButtons)
+        alignment.add(self._yearOfBirth)
+        table.attach(alignment, 1, 2, row, row+1)
+        label.set_mnemonic_widget(self._yearOfBirth)
+
+        row += 1
+
+        labelAlignment = gtk.Alignment(xalign = 1.0, yalign = 0.5,
+                                       xscale = 0.0, yscale = 0.0)
+        label = gtk.Label(xstr("register_email"))
+        label.set_use_underline(True)
+        labelAlignment.add(label)
+        table.attach(labelAlignment, 0, 1, row, row+1)
+
+        self._emailAddress = gtk.Entry()
+        self._emailAddress.connect("changed", self._updateButtons)
+        self._emailAddress.set_tooltip_text(xstr("register_email_tooltip"))
+        table.attach(self._emailAddress, 1, 2, row, row+1)
+        label.set_mnemonic_widget(self._emailAddress)
+
+        self._emailAddressPublic = gtk.CheckButton(xstr("register_email_public"))
+        self._emailAddressPublic.set_use_underline(True)
+        self._emailAddressPublic.set_tooltip_text(xstr("register_email_public_tooltip"))
+        table.attach(self._emailAddressPublic, 2, 3, row, row+1)
+
+        row += 1
+
+        labelAlignment = gtk.Alignment(xalign = 1.0, yalign = 0.5,
+                                       xscale = 0.0, yscale = 0.0)
+        label = gtk.Label(xstr("register_vatsim_id"))
+        label.set_use_underline(True)
+        labelAlignment.add(label)
+        table.attach(labelAlignment, 0, 1, row, row+1)
+
+        alignment = gtk.Alignment(xalign = 0.0, yalign = 0.0,
+                                  xscale = 0.0, yscale = 0.0)
+        self._vatsimID = IntegerEntry()
+        self._vatsimID.connect("changed", self._updateButtons)
+        self._vatsimID.set_tooltip_text(xstr("register_vatsim_id_tooltip"))
+        self._vatsimID.set_width_chars(7)
+        alignment.add(self._vatsimID)
+        table.attach(alignment, 1, 2, row, row+1)
+        label.set_mnemonic_widget(self._vatsimID)
+
+        row += 1
+
+        labelAlignment = gtk.Alignment(xalign = 1.0, yalign = 0.5,
+                                       xscale = 0.0, yscale = 0.0)
+        label = gtk.Label(xstr("register_ivao_id"))
+        label.set_use_underline(True)
+        labelAlignment.add(label)
+        table.attach(labelAlignment, 0, 1, row, row+1)
+
+        alignment = gtk.Alignment(xalign = 0.0, yalign = 0.0,
+                                  xscale = 0.0, yscale = 0.0)
+        self._ivaoID = IntegerEntry()
+        self._ivaoID.connect("changed", self._updateButtons)
+        self._ivaoID.set_tooltip_text(xstr("register_ivao_id_tooltip"))
+        self._ivaoID.set_width_chars(7)
+        alignment.add(self._ivaoID)
+        table.attach(alignment, 1, 2, row, row+1)
+        label.set_mnemonic_widget(self._ivaoID)
+
+        row += 1
+
+        labelAlignment = gtk.Alignment(xalign = 1.0, yalign = 0.5,
+                                       xscale = 0.0, yscale = 0.0)
+        label = gtk.Label(xstr("register_phone_num"))
+        label.set_use_underline(True)
+        label.set_use_markup(True)
+        labelAlignment.add(label)
+        table.attach(labelAlignment, 0, 1, row, row+1)
+
+        self._phoneNumber = gtk.Entry()
+        self._phoneNumber.set_tooltip_text(xstr("register_phone_num_tooltip"))
+        table.attach(self._phoneNumber, 1, 2, row, row+1)
+        label.set_mnemonic_widget(self._phoneNumber)
+
+        row += 1
+
+        labelAlignment = gtk.Alignment(xalign = 1.0, yalign = 0.5,
+                                       xscale = 0.0, yscale = 0.0)
+        label = gtk.Label(xstr("register_nationality"))
+        label.set_use_underline(True)
+        label.set_use_markup(True)
+        labelAlignment.add(label)
+        table.attach(labelAlignment, 0, 1, row, row+1)
+
+
+        self._nationality = gtk.Entry()
+        self._nationality.set_tooltip_text(xstr("register_nationality_tooltip"))
+        table.attach(self._nationality, 1, 2, row, row+1)
+        label.set_mnemonic_widget(self._nationality)
+
+        placeholder = gtk.Label()
+        placeholder.set_text(xstr("register_password_mismatch"))
+        placeholder.set_use_markup(True)
+        placeholder.set_child_visible(False)
+        placeholder.hide()
+        table.attach(placeholder, 2, 3, row, row+1)
+
+        row += 1
+
+        labelAlignment = gtk.Alignment(xalign = 1.0, yalign = 0.5,
+                                       xscale = 0.0, yscale = 0.0)
+        label = gtk.Label(xstr("register_password"))
+        label.set_use_underline(True)
+        labelAlignment.add(label)
+        table.attach(labelAlignment, 0, 1, row, row+1)
+
+        self._password = gtk.Entry()
+        self._password.set_visibility(False)
+        self._password.connect("changed", self._updateButtons)
+        self._password.set_tooltip_text(xstr("register_password_tooltip"))
+        table.attach(self._password, 1, 2, row, row+1)
+        label.set_mnemonic_widget(self._password)
+
+        alignment = gtk.Alignment(xalign = 0.0, yalign = 0.5,
+                                  xscale = 0.0, yscale = 0.0)
+        self._passwordStatus = gtk.Label()
+        alignment.add(self._passwordStatus)
+        table.attach(alignment, 2, 3, row, row+1)
+
+        row += 1
+
+        labelAlignment = gtk.Alignment(xalign = 1.0, yalign = 0.5,
+                                       xscale = 0.0, yscale = 0.0)
+        label = gtk.Label(xstr("register_password2"))
+        label.set_use_underline(True)
+        labelAlignment.add(label)
+        table.attach(labelAlignment, 0, 1, row, row+1)
+
+        self._password2 = gtk.Entry()
+        self._password2.set_visibility(False)
+        self._password2.connect("changed", self._updateButtons)
+        self._password2.set_tooltip_text(xstr("register_password2_tooltip"))
+        table.attach(self._password2, 1, 2, row, row+1)
+        label.set_mnemonic_widget(self._password2)
+
+        alignment = gtk.Alignment(xalign = 0.0, yalign = 0.5,
+                                  xscale = 0.0, yscale = 0.0)
+        self._password2Status = gtk.Label()
+        alignment.add(self._password2Status)
+        table.attach(alignment, 2, 3, row, row+1)
+
+        row += 1
+
+        self._rememberButton = gtk.CheckButton(xstr("remember_password"))
+        self._rememberButton.set_use_underline(True)
+        self._rememberButton.set_tooltip_text(xstr("login_remember_tooltip"))
+        table.attach(self._rememberButton, 1, 2, row, row+1)
+
+        cancelButton = \
+          self.addButton(xstr("button_cancel"))
+        cancelButton.connect("clicked", self._cancelClicked)
+
+        self._registerButton = \
+          self.addButton(xstr("button_register"), default = True,
+                              tooltip = xstr("button_register_tooltip"))
+        self._registerButton.connect("clicked", self._registerClicked)
+
+        self._updateButtons()
+
+    @property
+    def name1(self):
+        """Get the first name component entered."""
+        return self._name1.get_text()
+
+    @property
+    def name2(self):
+        """Get the second name component entered."""
+        return self._name2.get_text()
+
+    @property
+    def yearOfBirth(self):
+        """Get the year of birth."""
+        yearOfBirthText = self._yearOfBirth.get_text()
+        return int(yearOfBirthText) if yearOfBirthText else 0
+
+    @property
+    def emailAddress(self):
+        """Get the e-mail address."""
+        return self._emailAddress.get_text()
+
+    @property
+    def emailAddressPublic(self):
+        """Get the whether the e-mail address is public."""
+        return self._emailAddressPublic.get_active()
+
+    @property
+    def vatsimID(self):
+        """Get the VATSIM ID."""
+        return self._vatsimID.get_int()
+
+    @property
+    def ivaoID(self):
+        """Get the IVAO ID."""
+        return self._ivaoID.get_int()
+
+    @property
+    def phoneNumber(self):
+        """Get the phone number."""
+        return self._phoneNumber.get_text()
+
+    @property
+    def nationality(self):
+        """Get the nationality."""
+        return self._nationality.get_text()
+
+    @property
+    def password(self):
+        """Get the password."""
+        return self._password.get_text()
+
+    @property
+    def rememberPassword(self):
+        """Get whether the password should be remembered."""
+        return self._rememberButton.get_active()
+
+    def activate(self):
+        """Setup the route from the booked flight."""
+        self._yearOfBirth.set_value(0)
+        self._yearOfBirth.set_text("")
+        self._updateButtons()
+
+    def _updateButtons(self, widget = None):
+        """Update the sensitive state of the buttons"""
+        yearOfBirth = self.yearOfBirth
+
+        emailAddress = self.emailAddress
+        emailAddressMatch = RegisterPage._emailAddressRE.match(emailAddress)
+
+        vatsimID = self.vatsimID
+        ivaoID = self.ivaoID
+
+        password = self.password
+        password2 = self._password2.get_text()
+        if not password:
+            self._passwordStatus.set_text("")
+        elif len(password)<5:
+            self._passwordStatus.set_text(xstr("register_password_too_short"))
+        else:
+            self._passwordStatus.set_text(xstr("register_password_ok"))
+        self._passwordStatus.set_use_markup(True)
+
+        if len(password)<5 or not password2:
+            self._password2Status.set_text("")
+        elif password!=password2:
+            self._password2Status.set_text(xstr("register_password_mismatch"))
+        else:
+            self._password2Status.set_text(xstr("register_password_ok"))
+        self._password2Status.set_use_markup(True)
+
+        sensitive = \
+            len(self.name1)>0 and len(self.name2)>0 and \
+            yearOfBirth>=RegisterPage._minYearOfBirth and \
+            yearOfBirth<=RegisterPage._maxYearOfBirth and \
+            emailAddressMatch is not None and \
+            (vatsimID>=800000 or ivaoID>=100000) and \
+            len(password)>=5 and password==password2
+
+        self._registerButton.set_sensitive(sensitive)
+
+    def _cancelClicked(self, button):
+        """Called when the Cancel button is clicked."""
+        self.goBack()
+
+    def _registerClicked(self, button):
+        """Called when the Register button is clicked."""
+        nameOrder = xstr("register_nameorder")
+
+        if nameOrder=="eastern":
+            surName = self.name1
+            firstName = self.name2
+        else:
+            surName = self.name2
+            firstName = self.name1
+
+        nationality = self.nationality.lower()
+
+        if getLanguage().lower()=="hu" or nationality.find("hung")!=-1 or \
+           nationality.find("magyar")!=-1:
+            requestedNameOrder = "eastern"
+        else:
+            requestedNameOrder = "western"
+
+        registrationData = web.Registration(surName, firstName,
+                                            requestedNameOrder,
+                                            self.yearOfBirth,
+                                            self.emailAddress,
+                                            self.emailAddressPublic,
+                                            self.vatsimID, self.ivaoID,
+                                            self.phoneNumber, self.nationality,
+                                            self.password)
+        print "Registering with data:"
+        print "  name:", self.name1, self.name2, registrationData.firstName, registrationData.surName, requestedNameOrder
+        print "  yearOfBirth:", self.yearOfBirth, registrationData.yearOfBirth
+        print "  emailAddress:", self.emailAddress, registrationData.emailAddress
+        print "  emailAddressPublic:", self.emailAddressPublic, registrationData.emailAddressPublic
+        print "  vatsimID:", self.vatsimID, registrationData.vatsimID
+        print "  ivaoID:", self.ivaoID, registrationData.ivaoID
+        print "  phoneNumber:", self.phoneNumber, registrationData.phoneNumber
+        print "  nationality:", self.nationality, registrationData.nationality
+
+        gui = self._wizard.gui
+        gui.beginBusy(xstr("register_busy"))
+        gui.webHandler.register(self._registerResultCallback, registrationData)
+
+    def _registerResultCallback(self, returned, result):
+        """Called when the registration result is available."""
+        gobject.idle_add(self._handleRegisterResult, returned, result)
+
+    def _handleRegisterResult(self, returned, result):
+        """Handle the registration result."""
+        gui = self._wizard.gui
+
+        gui.endBusy()
+
+        print "Registration result:"
+        print "  returned:", returned
+        if returned:
+            print "  registered:", result.registered
+            if result.registered:
+                print "  pilotID", result.pilotID
+                print "  loggedIn", result.loggedIn
+            print "  emailAlreadyRegistered:", result.emailAlreadyRegistered
+            print "  invalidData:", result.invalidData
+
+        registrationOK = returned and result.registered
+
+        message = xstr("register_ok") if registrationOK \
+                  else xstr("register_failed")
+        secondaryMessage = None
+        if registrationOK:
+            if result.loggedIn:
+                secondaryMessage = xstr("register_info") % (result.pilotID,)
+            else:
+                secondaryMessage = xstr("register_nologin") % (result.pilotID,)
+            messageType = MESSAGETYPE_INFO
+
+            config = gui.config
+            config.pilotID = result.pilotID
+            config.rememberPassword = self.rememberPassword
+            if config.rememberPassword:
+                config.password = self.password
+            else:
+                config.password = ""
+
+            config.save()
+        elif returned and result.emailAlreadyRegistered:
+            secondaryMessage = xstr("register_email_already")
+            messageType = MESSAGETYPE_ERROR
+        elif returned and result.invalidData:
+            secondaryMessage = xstr("register_invalid_data")
+            messageType = MESSAGETYPE_ERROR
+        else:
+            secondaryMessage = xstr("register_error")
+            messageType = MESSAGETYPE_ERROR
+
+        dialog = gtk.MessageDialog(parent = gui.mainWindow,
+                                   type = messageType,
+                                   message_format = message)
+        dialog.set_title(WINDOW_TITLE_BASE + " - " +
+                         xstr("register_result_title"))
+        dialog.format_secondary_markup(secondaryMessage)
+
+        dialog.add_button(xstr("button_ok"), 0)
+
+        dialog.run()
+        dialog.hide()
+
+        if registrationOK:
+            if result.loggedIn:
+                self._wizard.nextPage()
+            else:
+                self._wizard.jumpPage("login")
+
+#-----------------------------------------------------------------------------
+
+class StudentPage(Page):
+    """A page displayed to students after logging in."""
+    _entryExamStatusQueryInterval = 60*1000
+
+    def __init__(self, wizard):
+        """Construct the student page."""
+        super(StudentPage, self).__init__(wizard, "student",
+                                          xstr("student_title"),
+                                          xstr("student_help"))
+
+        alignment = gtk.Alignment(xalign = 0.5, yalign = 0.5,
+                                  xscale = 0.5, yscale = 0.0)
+
+        table = gtk.Table(2, 2)
+        table.set_row_spacings(4)
+        table.set_col_spacings(32)
+        table.set_homogeneous(False)
+        alignment.add(table)
+        self.setMainWidget(alignment)
+
+        row = 0
+
+        labelAlignment = gtk.Alignment(xalign=0.0, yalign = 0.5,
+                                       xscale=0.0, yscale = 0.0)
+        label = gtk.Label(xstr("student_entry_exam_status"))
+        labelAlignment.add(label)
+        table.attach(labelAlignment, 0, 1, row, row + 1, xoptions = 0)
+
+        alignment = gtk.Alignment(xalign=0.0, yalign = 0.5,
+                                  xscale=1.0, yscale = 0.0)
+        self._entryExamStatus = gtk.Label()
+        self._entryExamStatus.set_use_markup(True)
+        self._entryExamStatus.set_alignment(0.0, 0.5)
+        alignment.add(self._entryExamStatus)
+        table.attach(alignment, 1, 2, row, row + 1)
+
+        row += 1
+
+        buttonAlignment = gtk.Alignment(xalign=0.0, xscale=1.0)
+        button = self._entryExamButton = gtk.Button(xstr("student_entry_exam"))
+        button.set_use_underline(True)
+        button.connect("clicked", self._entryExamClicked)
+        button.set_tooltip_text(xstr("student_entry_exam_tooltip"))
+
+        buttonAlignment.add(button)
+        table.attach(buttonAlignment, 0, 2, row, row + 1,
+                     ypadding = 4)
+
+    def activate(self):
+        """Activate the student page."""
+        loginResult = self._wizard.loginResult
+        self._entryExamLink = loginResult.entryExamLink
+
+        self._updateEntryExamStatus(loginResult.entryExamPassed)
+
+        self._getEntryExamStatus()
+
+    def _entryExamClicked(self, button):
+        """Called when the entry exam button is clicked."""
+        webbrowser.open(self._entryExamLink)
+
+    def _getEntryExamStatus(self):
+        """Initiate the query of the entry exam status after the interval."""
+        gobject.timeout_add(StudentPage._entryExamStatusQueryInterval,
+                            lambda: self._wizard.gui.webHandler. \
+                            getEntryExamStatus(self._entryExamStatusCallback))
+
+    def _entryExamStatusCallback(self, returned, result):
+        """Called when the entry exam status is available."""
+        gobject.idle_add(self._handleEntryExamStatus, returned, result)
+
+    def _handleEntryExamStatus(self, returned, result):
+        """Called when the entry exam status is availabe."""
+        print "_handleEntryExamStatus", returned, result
+        if returned:
+            self._entryExamLink = result.entryExamLink
+            self._updateEntryExamStatus(result.entryExamPassed)
+            self._getEntryExamStatus()
+
+    def _updateEntryExamStatus(self, passed):
+        """Update the entry exam status display and button."""
+        self._entryExamStatus.set_text(xstr("student_entry_exam_passed")
+                                       if passed else
+                                       xstr("student_entry_exam_not_passed"))
+        self._entryExamStatus.set_use_markup(True)
+        self._entryExamButton.set_sensitive(not passed)
 
 #-----------------------------------------------------------------------------
@@ -901,5 +1463,6 @@
                "Then press the Connect button to connect to the simulator."
         completedHelp = "The basic data of your flight can be read below."
-        super(ConnectPage, self).__init__(wizard, xstr("connect_title"),
+        super(ConnectPage, self).__init__(wizard, "connect",
+                                          xstr("connect_title"),
                                           xstr("connect_help"),
                                           completedHelp = xstr("connect_chelp"))
@@ -1084,5 +1647,6 @@
     def __init__(self, wizard):
         """Construct the page."""
-        super(PayloadPage, self).__init__(wizard, xstr("payload_title"),
+        super(PayloadPage, self).__init__(wizard, "payload",
+                                          xstr("payload_title"),
                                           xstr("payload_help"),
                                           completedHelp = xstr("payload_chelp"))
@@ -1311,5 +1875,6 @@
     current time from the flight simulator."""
     def __init__(self, wizard):
-        super(TimePage, self).__init__(wizard, xstr("time_title"),
+        super(TimePage, self).__init__(wizard, "time",
+                                       xstr("time_title"),
                                        xstr("time_help"),
                                        completedHelp = xstr("time_chelp"))
@@ -1432,5 +1997,6 @@
     def __init__(self, wizard):
         """Construct the page."""
-        super(RoutePage, self).__init__(wizard, xstr("route_title"),
+        super(RoutePage, self).__init__(wizard, "route",
+                                        xstr("route_title"),
                                         xstr("route_help"),
                                         completedHelp = xstr("route_chelp"))
@@ -1600,8 +2166,8 @@
         if self._wizard.gui.config.useSimBrief and \
            self._wizard.usingSimBrief is not False:
-            self._wizard.nextPage()
+            self._wizard.jumpPage("simbrief_setup")
         else:
             self._wizard.usingSimBrief = False
-            self._wizard.jumpPage(3)
+            self._wizard.jumpPage("fuel")
 
 #-----------------------------------------------------------------------------
@@ -1762,5 +2328,5 @@
         """Construct the setup page."""
 
-        super(SimBriefSetupPage, self).__init__(wizard,
+        super(SimBriefSetupPage, self).__init__(wizard, "simbrief_setup",
                                                 xstr("simbrief_setup_title"),
                                                 xstr("simbrief_setup_help"),
@@ -2081,5 +2647,5 @@
 
                 self._wizard.usingSimBrief = False
-                self._wizard.jumpPage(2, fromPageShift = 1)
+                self._wizard.jumpPage("fuel", fromPageShift = 1)
 
     def _getPlan(self):
@@ -2166,5 +2732,5 @@
         """Construct the setup page."""
 
-        super(SimBriefingPage, self).__init__(wizard,
+        super(SimBriefingPage, self).__init__(wizard, "simbrief_result",
                                               xstr("simbrief_result_title"), "")
 
@@ -2422,5 +2988,6 @@
     def __init__(self, wizard):
         """Construct the page."""
-        super(FuelPage, self).__init__(wizard, xstr("fuel_title"),
+        super(FuelPage, self).__init__(wizard, "fuel",
+                                       xstr("fuel_title"),
                                        xstr("fuel_help_pre") +
                                        xstr("fuel_help_post"),
@@ -2474,7 +3041,7 @@
             self._pump()
         elif self._wizard.usingSimBrief:
-            self._wizard.jumpPage(3)
+            self._wizard.jumpPage("takeoff")
         else:
-            self._wizard.nextPage()
+            self._wizard.jumpPage("briefing1")
 
     def _setupTanks(self, tankData):
@@ -2522,5 +3089,5 @@
             if self._wizard.usingSimBrief:
                 self._wizard.gui.startMonitoring()
-                self._wizard.jumpPage(3)
+                self._wizard.jumpPage("takeoff")
             else:
                 bookedFlight = self._wizard._bookedFlight
@@ -2591,9 +3158,13 @@
         self._departure = departure
 
-        title = xstr("briefing_title") % (1 if departure else 2,
+        number = 1 if departure else 2
+
+        title = xstr("briefing_title") % (number,
                                           xstr("briefing_departure")
                                           if departure
                                           else xstr("briefing_arrival"))
-        super(BriefingPage, self).__init__(wizard, title, xstr("briefing_help"),
+        super(BriefingPage, self).__init__(wizard,
+                                           "briefing%d" % (number,),
+                                           title, xstr("briefing_help"),
                                            completedHelp = xstr("briefing_chelp"))
 
@@ -2782,5 +3353,6 @@
     def __init__(self, wizard):
         """Construct the takeoff page."""
-        super(TakeoffPage, self).__init__(wizard, xstr("takeoff_title"),
+        super(TakeoffPage, self).__init__(wizard, "takeoff",
+                                          xstr("takeoff_title"),
                                           xstr("takeoff_help"),
                                           completedHelp = xstr("takeoff_chelp"))
@@ -3270,5 +3842,6 @@
     def __init__(self, wizard):
         """Construct the page."""
-        super(CruisePage, self).__init__(wizard, xstr("cruise_title"),
+        super(CruisePage, self).__init__(wizard, "cruise",
+                                         xstr("cruise_title"),
                                          xstr("cruise_help"))
 
@@ -3384,5 +3957,6 @@
     def __init__(self, wizard):
         """Construct the landing page."""
-        super(LandingPage, self).__init__(wizard, xstr("landing_title"),
+        super(LandingPage, self).__init__(wizard, "landing",
+                                          xstr("landing_title"),
                                           xstr("landing_help"),
                                           completedHelp = xstr("landing_chelp"))
@@ -3698,5 +4272,6 @@
         """Construct the finish page."""
         help = xstr("finish_help") + xstr("finish_help_goodtime")
-        super(FinishPage, self).__init__(wizard, xstr("finish_title"), help)
+        super(FinishPage, self).__init__(wizard, "finish",
+                                         xstr("finish_title"), help)
 
         alignment = gtk.Alignment(xalign = 0.5, yalign = 0.5,
@@ -4256,4 +4831,6 @@
         self._pages.append(FlightSelectionPage(self))
         self._pages.append(GateSelectionPage(self))
+        self._pages.append(RegisterPage(self))
+        self._pages.append(StudentPage(self))
         self._pages.append(ConnectPage(self))
         self._payloadPage = PayloadPage(self)
@@ -4328,5 +4905,5 @@
     def entranceExam(self):
         """Get whether an entrance exam is about to be taken."""
-        return self._loginPage.entranceExam
+        return self._loginResult is not None and self._loginResult.rank=="STU"
 
     @property
@@ -4551,9 +5128,14 @@
     def nextPage(self, finalize = True):
         """Go to the next page."""
-        self.jumpPage(1, finalize)
-
-    def jumpPage(self, count, finalize = True, fromPageShift = None):
+        nextPageID = self._pages[self._currentPage].nextPageID
+        self.jumpPage(1 if nextPageID is None else nextPageID, finalize)
+
+    def jumpPage(self, countOrID, finalize = True, fromPageShift = None):
         """Go to the page which is 'count' pages after the current one."""
-        self.setCurrentPage(self._currentPage + count,
+        if isinstance(countOrID, str):
+            targetIndex = self._getIndexOf(countOrID)
+        else:
+            targetIndex = self._currentPage + countOrID
+        self.setCurrentPage(targetIndex,
                             finalize = finalize, fromPageShift = fromPageShift)
 
@@ -4616,5 +5198,5 @@
         #self.setCurrentPage(10)
 
-    def login(self, callback, pilotID, password, entranceExam):
+    def login(self, callback, pilotID, password):
         """Called when the login button was clicked."""
         self._loginCallback = callback
@@ -4624,5 +5206,4 @@
             pilotID = loginResult.pilotID
             password = loginResult.password
-            entranceExam = loginResult.entranceExam
             busyMessage = xstr("reload_busy")
         else:
@@ -4633,6 +5214,5 @@
 
         self.gui.webHandler.login(self._loginResultCallback,
-                                  pilotID, password,
-                                  entranceExam = entranceExam)
+                                  pilotID, password)
 
     def reloadFlights(self, callback):
@@ -4682,5 +5262,4 @@
         """Handle the login result."""
         self.gui.endBusy()
-        returned = True
         isReload = self._loginResult is not None
         if returned:
@@ -4788,3 +5367,13 @@
                 self._arrivalBriefingPage.setMETAR(metar)
 
+    def _getIndexOf(self, pageID):
+        """Get the index for the given page ID.
+
+        It is an assertion failure if the ID is not found."""
+        for index in range(0, len(self._pages)):
+            page = self._pages[index]
+            if page.id==pageID:
+                return index
+        assert False
+
 #-----------------------------------------------------------------------------
Index: src/mlx/gui/gui.py
===================================================================
--- src/mlx/gui/gui.py	(revision 733)
+++ src/mlx/gui/gui.py	(revision 755)
@@ -90,5 +90,10 @@
         self._sendBugReportCallback = None
 
-        self.webHandler = web.Handler()
+        self._credentialsCondition = threading.Condition()
+        self._credentialsAvailable = False
+        self._credentialsUserName = None
+        self._credentialsPassword = None
+
+        self.webHandler = web.Handler(config, self._getCredentialsCallback)
         self.webHandler.start()
 
@@ -442,4 +447,6 @@
                                     self._mainWindow)
             self._updater.start()
+        else:
+            self.updateDone()
 
         cef.initialize(self._programDirectory, self._cefInitialized)
@@ -452,4 +459,8 @@
 
         self._disconnect()
+
+    def updateDone(self):
+        """Called when the update is done (and there is no need to restart)."""
+        gobject.idle_add(self._updateDone)
 
     def connected(self, fsType, descriptor):
@@ -1635,2 +1646,78 @@
         """Set the anti-ice on indicator."""
         self._wizard.landingAntiIceOn = value
+
+    def _getCredentialsCallback(self):
+        """Called when the web handler asks for the credentials."""
+        # FIXME: this is almost the same as
+        # SimBriefSetupPage._getCredentialsCallback
+        with self._credentialsCondition:
+            self._credentialsAvailable = False
+
+            gobject.idle_add(self._getCredentials)
+
+            while not self._credentialsAvailable:
+                self._credentialsCondition.wait()
+
+            return (self._credentialsUserName, self._credentialsPassword)
+
+    def _getCredentials(self):
+        """Get the credentials."""
+        # FIXME: this is almost the same as
+        # SimBriefSetupPage._getCredentials
+        with self._credentialsCondition:
+            config = self.config
+
+            dialog = CredentialsDialog(self, config.pilotID, config.password,
+                                       xstr("login_title"),
+                                       xstr("button_cancel"),
+                                       xstr("button_ok"),
+                                       xstr("label_pilotID"),
+                                       xstr("login_pilotID_tooltip"),
+                                       xstr("label_password"),
+                                       xstr("login_password_tooltip"),
+                                       xstr("login_info"),
+                                       config.rememberPassword,
+                                       xstr("remember_password"),
+                                       xstr("login_remember_tooltip"))
+            response = dialog.run()
+
+            if response==RESPONSETYPE_OK:
+                self._credentialsUserName = dialog.userName
+                self._credentialsPassword = dialog.password
+                rememberPassword = dialog.rememberPassword
+
+                config.pilotID = self._credentialsUserName
+
+                config.password = \
+                  self._credentialsPassword if rememberPassword else ""
+                config.rememberPassword = rememberPassword
+
+                config.save()
+            else:
+                self._credentialsUserName = None
+                self._credentialsPassword = None
+
+            self._credentialsAvailable = True
+            self._credentialsCondition.notify()
+
+    def _updateDone(self):
+        """Called when the update is done.
+
+        It checks if we already know the PID, and if not, asks the user whether
+        to register."""
+        if not self.config.pilotID and not self.config.password:
+            dialog = gtk.MessageDialog(parent = self._mainWindow,
+                                       type = MESSAGETYPE_QUESTION,
+                                       message_format = xstr("register_ask"))
+
+            dialog.set_title(WINDOW_TITLE_BASE)
+            dialog.format_secondary_markup(xstr("register_ask_sec"))
+
+            dialog.add_button(xstr("button_cancel"), 0)
+            dialog.add_button(xstr("button_register"), 1)
+            dialog.set_default_response(1)
+
+            result = dialog.run()
+            dialog.hide()
+            if result == 1:
+                self._wizard.jumpPage("register")
Index: src/mlx/gui/prefs.py
===================================================================
--- src/mlx/gui/prefs.py	(revision 684)
+++ src/mlx/gui/prefs.py	(revision 738)
@@ -276,4 +276,6 @@
 
         self._updateURL.set_text(config.updateURL)
+
+        self._useRPC.set_active(config.useRPC)
 
         self._settingFromConfig = False
@@ -320,4 +322,5 @@
         config.autoUpdate = self._autoUpdate.get_active()
         config.updateURL = self._updateURL.get_text()
+        config.useRPC = self._useRPC.get_active()
 
     def _buildGeneral(self):
@@ -814,4 +817,10 @@
         updateURLBox.pack_start(self._updateURL, True, True, 4)
 
+        self._useRPC = gtk.CheckButton(xstr("prefs_use_rpc"))
+        mainBox.pack_start(self._useRPC, False, False, 16)
+
+        self._useRPC.set_use_underline(True)
+        self._useRPC.set_tooltip_text(xstr("prefs_use_rpc_tooltip"))
+
         return mainAlignment
 
Index: src/mlx/gui/update.py
===================================================================
--- src/mlx/gui/update.py	(revision 300)
+++ src/mlx/gui/update.py	(revision 755)
@@ -124,4 +124,5 @@
         if not self._waitAfterFinish:
             gobject.idle_add(self._progressWindow.hide)
+            self._gui.updateDone()
 
     def downloadingManifest(self):
@@ -286,4 +287,6 @@
         if self._restart:
             self._gui.restart()
+        else:
+            self._gui.updateDone()
 
 #-------------------------------------------------------------------------------
Index: src/mlx/pirep.py
===================================================================
--- src/mlx/pirep.py	(revision 437)
+++ src/mlx/pirep.py	(revision 743)
@@ -4,4 +4,6 @@
 import const
 import cPickle as pickle
+import datetime
+import time
 
 #------------------------------------------------------------------------------
@@ -19,4 +21,9 @@
 class PIREP(object):
     """A pilot's report of a flight."""
+    _flightTypes = { const.FLIGHTTYPE_SCHEDULED : "SCHEDULED",
+                     const.FLIGHTTYPE_OLDTIMER : "OT",
+                     const.FLIGHTTYPE_VIP : "VIP",
+                     const.FLIGHTTYPE_CHARTER : "CHARTER" }
+
     @staticmethod
     def _formatLine(timeStr, line):
@@ -24,4 +31,9 @@
         some other things."""
         return "[" + timeStr + "]-[" + line + "]"
+
+    @staticmethod
+    def formatTimestampForRPC(t):
+        """Format the given timestamp for RPC."""
+        return time.strftime("%Y-%m-%d %H:%M:%S", time.gmtime(t))
 
     @staticmethod
@@ -91,4 +103,29 @@
         self.faultLineIndexes = logger.faultLineIndexes
 
+    @property
+    def flightTypeText(self):
+        """Get the text representation of the flight type."""
+        return PIREP._flightTypes[self.flightType]
+
+    @property
+    def blockTimeStartText(self):
+        """Get the beginning of the block time in string format."""
+        return PIREP.formatTimestampForRPC(self.blockTimeStart)
+
+    @property
+    def flightTimeStartText(self):
+        """Get the beginning of the flight time in string format."""
+        return PIREP.formatTimestampForRPC(self.flightTimeStart)
+
+    @property
+    def flightTimeEndText(self):
+        """Get the end of the flight time in string format."""
+        return PIREP.formatTimestampForRPC(self.flightTimeEnd)
+
+    @property
+    def blockTimeEndText(self):
+        """Get the end of the block time in string format."""
+        return PIREP.formatTimestampForRPC(self.blockTimeEnd)
+
     def getACARSText(self):
         """Get the ACARS text.
@@ -152,2 +189,39 @@
             print u"Failed saving PIREP to %s: %s" % (path, error)
             return error
+
+    def _serialize(self):
+        """Serialize the PIREP for JSON-RPC."""
+        attrs = {}
+        attrs["log"] = self.getACARSText()
+        attrs["numPassengers"] = self.numPassengers
+        attrs["numCrew"] = self.numCrew
+        attrs["cargoWeight"] = self.cargoWeight
+        attrs["bagWeight"] = self.bagWeight
+        attrs["mailWeight"] = self.mailWeight
+        attrs["flightType"] = self.flightTypeText
+        attrs["online"] = 1 if self.online else 0
+        attrs["blockTimeStart"] = self.blockTimeStartText
+        attrs["blockTimeEnd"] = self.blockTimeEndText
+        attrs["flightTimeStart"] = self.flightTimeStartText
+        attrs["flightTimeEnd"] = self.flightTimeEndText
+        attrs["timeComment"] = self.getTimeComment()
+        attrs["fuelUsed"] = self.fuelUsed
+        attrs["departureRunway"] = self.departureRunway
+        attrs["arrivalRunway"] = self.arrivalRunway
+        attrs["departureMETAR"] = self.departureMETAR
+        attrs["arrivalMETAR"] = self.arrivalMETAR
+        attrs["filedCruiseLevel"] = self.filedCruiseAltitude / 100.0
+        attrs["cruiseLevel"] = self.cruiseAltitude / 100.0
+        attrs["sid"] = self.sid
+        attrs["route"] = self.route
+        attrs["star"] = self.star
+        attrs["approachType"] = self.approachType
+        attrs["comments"] = self.comments
+        attrs["flightDefects"] = self.flightDefects
+        attrs["ratingText"] = self.getRatingText()
+        attrs["rating"] = max(0.0, self.rating)
+        attrs["flownDistance"] = self.flownDistance
+        # FIXME: it should be stored in the PIREP when it is sent later
+        attrs["flightDate"] = datetime.date.today().strftime("%Y-%m-%d")
+
+        return ([], attrs)
Index: src/mlx/rpc.py
===================================================================
--- src/mlx/rpc.py	(revision 764)
+++ src/mlx/rpc.py	(revision 764)
@@ -0,0 +1,338 @@
+import const
+import rpccommon
+
+from common import MAVA_BASE_URL
+
+import jsonrpclib
+import hashlib
+import datetime
+
+#---------------------------------------------------------------------------------------
+
+class RPCObject(object):
+    """Base class for objects read from RPC calls.
+
+    It is possible to construct it from a dictionary."""
+    def __init__(self, value, instructions = {}):
+        """Construct the object.
+
+        value is the dictionary returned by the call.
+
+        info is a mapping from names to 'instructions' on what to do with the
+        corresponding values. If the instruction is None, it will be ignored.
+        If the instruction is a function, the value will be passed to it and
+        the return value will be stored in the object.
+
+        For all other names, the value will be stored as the same-named
+        attribute."""
+        for (key, value) in value.iteritems():
+            if key in instructions:
+                instruction = instructions[key]
+                if instruction is None:
+                    continue
+
+                value = instruction(value)
+            setattr(self, key, value)
+
+#---------------------------------------------------------------------------------------
+
+class Reply(RPCObject):
+    """The generic reply structure."""
+
+#---------------------------------------------------------------------------------------
+
+class BookedFlight(RPCObject):
+    """A booked flight."""
+    # FIXME: copied from web.BookedFlight
+    TYPECODE2TYPE = { "736"  : const.AIRCRAFT_B736,
+                      "73G"  : const.AIRCRAFT_B737,
+                      "738"  : const.AIRCRAFT_B738,
+                      "73H"  : const.AIRCRAFT_B738C,
+                      "733"  : const.AIRCRAFT_B733,
+                      "734"  : const.AIRCRAFT_B734,
+                      "735"  : const.AIRCRAFT_B735,
+                      "DH4"  : const.AIRCRAFT_DH8D,
+                      "762"  : const.AIRCRAFT_B762,
+                      "763"  : const.AIRCRAFT_B763,
+                      "CR2"  : const.AIRCRAFT_CRJ2,
+                      "F70"  : const.AIRCRAFT_F70,
+                      "LI2"  : const.AIRCRAFT_DC3,
+                      "TU3"  : const.AIRCRAFT_T134,
+                      "TU5"  : const.AIRCRAFT_T154,
+                      "YK4"  : const.AIRCRAFT_YK40,
+                      "146"  : const.AIRCRAFT_B462 }
+
+    # FIXME: copied from web.BookedFlight
+    @staticmethod
+    def _decodeAircraftType(typeCode):
+        """Decode the aircraft type from the given typeCode."""
+        if typeCode in BookedFlight.TYPECODE2TYPE:
+            return BookedFlight.TYPECODE2TYPE[typeCode]
+        else:
+            raise Exception("Invalid aircraft type code: '" + typeCode + "'")
+
+    # FIXME: copied from web.BookedFlight
+    @staticmethod
+    def getDateTime(date, time):
+        """Get a datetime object from the given textual date and time."""
+        return datetime.datetime.strptime(date + " " + time,
+                                          "%Y-%m-%d %H:%M:%S")
+
+    # The instructions for the construction
+    _instructions = {
+        "numPassengers" : int,
+        "numCrew" : int,
+        "bagWeight" : int,
+        "cargoWeight" : int,
+        "mailWeight" : int,
+        "aircraftType" : lambda value: BookedFlight._decodeAircraftType(value)
+        }
+
+    def __init__(self, value):
+        """Construct the booked flight object from the given RPC result
+        value."""
+        super(BookedFlight, self).__init__(value, BookedFlight._instructions)
+        self.departureTime = \
+          BookedFlight.getDateTime(self.date, self.departureTime)
+        self.arrivalTime = \
+          BookedFlight.getDateTime(self.date, self.arrivalTime)
+        if self.arrivalTime<self.departureTime:
+            self.arrivalTime += datetime.timedelta(days = 1)
+
+#---------------------------------------------------------------------------------------
+
+class Plane(rpccommon.Plane, RPCObject):
+    """An airplane in the fleet."""
+    _instructions = {
+        "status" : lambda value: rpccommon.Plane.str2status(value),
+        "gateNumber" : lambda value: value if value else None
+        }
+
+    def __init__(self, value):
+        """Construct the plane."""
+        RPCObject.__init__(self, value, instructions = Plane._instructions)
+
+#---------------------------------------------------------------------------------------
+
+class Fleet(rpccommon.Fleet):
+    """The fleet."""
+    def __init__(self, value):
+        """Construct the fleet."""
+        super(Fleet, self).__init__()
+        for planeValue in value:
+            self._addPlane(Plane(planeValue))
+
+#---------------------------------------------------------------------------------------
+
+class Registration(object):
+    """Data for registration."""
+    def __init__(self, surName, firstName, nameOrder,
+                 yearOfBirth, emailAddress, emailAddressPublic,
+                 vatsimID, ivaoID, phoneNumber, nationality, password):
+        """Construct the registration data."""
+        self.surName = surName
+        self.firstName = firstName
+        self.nameOrder = nameOrder
+        self.yearOfBirth = yearOfBirth
+        self.emailAddress = emailAddress
+        self.emailAddressPublic = 1 if emailAddressPublic is True else \
+          0 if emailAddressPublic is False else emailAddressPublic
+        self.vatsimID = "" if vatsimID is None else vatsimID
+        self.ivaoID = "" if ivaoID is None else ivaoID
+        self.phoneNumber = phoneNumber
+        self.nationality = nationality
+        self.password = password
+
+#---------------------------------------------------------------------------------------
+
+class RPCException(Exception):
+    """An exception thrown by RPC operations."""
+    def __init__(self, result, message = None):
+        """Construct the exception."""
+        self._result = result
+        if message is None:
+            message = "RPC call failed with result code: %d" % (result,)
+        super(RPCException, self).__init__(message)
+
+    @property
+    def result(self):
+        """Get the result code."""
+        return self._result
+
+#---------------------------------------------------------------------------------------
+
+class Client(object):
+    """The RPC client interface."""
+    # Result code: OK
+    RESULT_OK = 0
+
+    # Result code: the login has failed
+    RESULT_LOGIN_FAILED = 1
+
+    # Result code: the given session ID is unknown (it might have expired).
+    RESULT_SESSION_INVALID = 2
+
+    # Result code: some database error
+    RESULT_DATABASE_ERROR = 3
+
+    # Result code: invalid data
+    RESULT_INVALID_DATA = 4
+
+    # Result code: the flight does not exist
+    RESULT_FLIGHT_NOT_EXISTS = 101
+
+    # Result code: the flight has already been reported.
+    RESULT_FLIGHT_ALREADY_REPORTED = 102
+
+    # Result code: a user with the given e-mail address already exists
+    RESULT_EMAIL_ALREADY_REGISTERED = 103
+
+    def __init__(self, getCredentialsFn):
+        """Construct the client."""
+        self._getCredentialsFn = getCredentialsFn
+
+        self._server = jsonrpclib.Server(MAVA_BASE_URL + "/jsonrpc.php")
+
+        self._userName = None
+        self._passwordHash = None
+        self._sessionID = None
+        self._loginCount = 0
+
+    @property
+    def valid(self):
+        """Determine if the client is valid, i.e. there is a session ID
+        stored."""
+        return self._sessionID is not None
+
+    def setCredentials(self, userName, password):
+        """Set the credentials for future logins."""
+
+        self._userName = userName
+
+        md5 = hashlib.md5()
+        md5.update(password)
+        self._passwordHash = md5.hexdigest()
+
+        self._sessionID = None
+
+    def register(self, registrationData):
+        """Register with the given data.
+
+        Returns a tuple of:
+        - the error code,
+        - the PID if there is no error."""
+        reply = Reply(self._server.register(registrationData))
+
+        return (reply.result,
+                reply.value["pid"] if reply.result==Client.RESULT_OK else None)
+
+    def login(self):
+        """Login using the given previously set credentials.
+
+        The session ID is stored in the object and used for later calls.
+
+        Returns the name of the pilot on success, or None on error."""
+        self._sessionID = None
+
+        reply = Reply(self._server.login(self._userName, self._passwordHash))
+        if reply.result == Client.RESULT_OK:
+            self._loginCount += 1
+            self._sessionID = reply.value["sessionID"]
+            return (reply.value["name"], reply.value["rank"])
+        else:
+            return None
+
+    def getFlights(self):
+        """Get the flights available for performing."""
+        flights = []
+
+        value = self._performCall(lambda sessionID:
+                                  self._server.getFlights(sessionID))
+        for flightData in value:
+            flights.append(BookedFlight(flightData))
+
+        flights.sort(cmp = lambda flight1, flight2:
+                     cmp(flight1.departureTime, flight2.departureTime))
+
+        return flights
+
+    def getEntryExamStatus(self):
+        """Get the status of the exams needed for joining MAVA."""
+        value = self._performCall(lambda sessionID:
+                                  self._server.getEntryExamStatus(sessionID))
+        return (value["entryExamPassed"], value["entryExamLink"],
+                value["checkFlightStatus"])
+
+    def getFleet(self):
+        """Query and return the fleet."""
+        value = self._performCall(lambda sessionID:
+                                  self._server.getFleet(sessionID))
+
+        return Fleet(value)
+
+    def updatePlane(self, tailNumber, status, gateNumber):
+        """Update the state and position of the plane with the given tail
+        number."""
+        status = rpccommon.Plane.status2str(status)
+        self._performCall(lambda sessionID:
+                          self._server.updatePlane(sessionID, tailNumber,
+                                                   status, gateNumber))
+
+    def addPIREP(self, flightID, pirep):
+        """Add the PIREP for the given flight."""
+        (result, _value) = \
+          self._performCall(lambda sessionID:
+                            self._server.addPIREP(sessionID, flightID, pirep),
+                            acceptResults = [Client.RESULT_FLIGHT_ALREADY_REPORTED,
+                                             Client.RESULT_FLIGHT_NOT_EXISTS])
+        return result
+
+    def updateOnlineACARS(self, acars):
+        """Update the online ACARS from the given data."""
+        self._performCall(lambda sessionID:
+                          self._server.updateOnlineACARS(sessionID, acars))
+
+    def _performCall(self, callFn, acceptResults = []):
+        """Perform a call using the given call function.
+
+        acceptResults should be a list of result codes that should be accepted
+        besides RESULT_OK. If this list is not empty, the returned value is a
+        tuple of the result code and the corresponding value. Otherwise only
+        RESULT_OK is accepted, and the value is returned.
+
+        All other error codes are converted to exceptions."""
+        numAttempts = 0
+        while True:
+            reply = Reply(callFn(self._ensureSession()))
+            numAttempts += 1
+            result = reply.result
+            if result==Client.RESULT_SESSION_INVALID:
+                self._sessionID = None
+                if numAttempts==3:
+                    raise RPCException(result)
+            elif result!=Client.RESULT_OK and result not in acceptResults:
+                raise RPCException(result)
+            elif acceptResults:
+                return (result, reply.value)
+            else:
+                return reply.value
+
+    def _ensureSession(self):
+        """Ensure that there is a valid session ID."""
+        while self._sessionID is None:
+            if self._userName is not None and self._passwordHash is not None:
+                if not self.login():
+                    self._userName = self._passwordHash = None
+
+            if self._userName is None or self._passwordHash is None:
+                (self._userName, password) = self._getCredentialsFn()
+                if self._userName is None:
+                    raise RPCException(Client.RESULT_LOGIN_FAILED)
+
+                md5 = hashlib.md5()
+                md5.update(password)
+                self._passwordHash = md5.hexdigest()
+
+        return self._sessionID
+
+#---------------------------------------------------------------------------------------
Index: src/mlx/rpccommon.py
===================================================================
--- src/mlx/rpccommon.py	(revision 742)
+++ src/mlx/rpccommon.py	(revision 742)
@@ -0,0 +1,93 @@
+# Some common objects for RPC communication
+
+#------------------------------------------------------------------------------
+
+import const
+
+#------------------------------------------------------------------------------
+
+class Plane(object):
+    """Information about an airplane in the fleet."""
+    @staticmethod
+    def str2status(letter):
+        """Convert the given letter into a plane status."""
+        return const.PLANE_HOME if letter=="H" else \
+               const.PLANE_AWAY if letter=="A" else \
+               const.PLANE_PARKING if letter=="P" else \
+               const.PLANE_UNKNOWN
+
+    @staticmethod
+    def status2str(status):
+        """Convert the given status into the corresponding letter code."""
+        return "H" if status==const.PLANE_HOME else \
+               "A" if status==const.PLANE_AWAY else \
+               "P" if status==const.PLANE_PARKING else ""
+
+    def _setStatus(self, letter):
+        """Set the status from the given letter."""
+        self.status = Plane.str2status(letter)
+
+    def __repr__(self):
+        """Get the representation of the plane object."""
+        s = "<Plane: %s %s" % (self.tailNumber,
+                               "home" if self.status==const.PLANE_HOME else \
+                               "away" if self.status==const.PLANE_AWAY else \
+                               "parking" if self.status==const.PLANE_PARKING \
+                               else "unknown")
+        if self.gateNumber is not None:
+            s += " (gate " + self.gateNumber + ")"
+        s += ">"
+        return s
+
+#------------------------------------------------------------------------------
+
+class Fleet(object):
+    """Information about the whole fleet."""
+    def __init__(self):
+        """Construct the fleet information by reading the given file object."""
+        self._planes = {}
+
+    def isGateConflicting(self, plane):
+        """Check if the gate of the given plane conflicts with another plane's
+        position."""
+        for p in self._planes.itervalues():
+            if p.tailNumber!=plane.tailNumber and \
+               p.status==const.PLANE_HOME and \
+               p.gateNumber==plane.gateNumber:
+                return True
+
+        return False
+
+    def getOccupiedGateNumbers(self):
+        """Get a set containing the numbers of the gates occupied by planes."""
+        gateNumbers = set()
+        for p in self._planes.itervalues():
+            if p.status==const.PLANE_HOME and p.gateNumber:
+                gateNumbers.add(p.gateNumber)
+        return gateNumbers
+
+    def updatePlane(self, tailNumber, status, gateNumber = None):
+        """Update the status of the given plane."""
+        if tailNumber in self._planes:
+            plane = self._planes[tailNumber]
+            plane.status = status
+            plane.gateNumber = gateNumber
+
+    def _addPlane(self, plane):
+        """Add the given plane to the fleet."""
+        self._planes[plane.tailNumber] = plane
+
+    def __iter__(self):
+        """Get an iterator over the planes."""
+        for plane in self._planes.itervalues():
+            yield plane
+
+    def __getitem__(self, tailNumber):
+        """Get the plane with the given tail number.
+
+        If the plane is not in the fleet, None is returned."""
+        return self._planes[tailNumber] if tailNumber in self._planes else None
+
+    def __repr__(self):
+        """Get the representation of the fleet object."""
+        return self._planes.__repr__()
Index: src/mlx/web.py
===================================================================
--- src/mlx/web.py	(revision 570)
+++ src/mlx/web.py	(revision 764)
@@ -2,4 +2,9 @@
 import const
 import util
+from rpc import Registration
+import rpc
+import rpccommon
+
+from common import MAVA_BASE_URL
 
 import threading
@@ -255,5 +260,5 @@
 #------------------------------------------------------------------------------
 
-class Plane(object):
+class Plane(rpccommon.Plane):
     """Information about an airplane in the fleet."""
     def __init__(self, s):
@@ -263,4 +268,6 @@
         The first field is the tail number, the second field is the gate
         number, the third field is the plane's status as a character."""
+        super(Plane, self).__init__()
+
         try:
             words = s.split(" ")
@@ -269,8 +276,5 @@
 
             status = words[2] if len(words)>2 else None
-            self.status = const.PLANE_HOME if status=="H" else \
-                          const.PLANE_AWAY if status=="A" else \
-                          const.PLANE_PARKING if status=="P" else \
-                          const.PLANE_UNKNOWN
+            self._setStatus(status)
 
             gateNumber = words[1] if len(words)>1 else ""
@@ -281,24 +285,12 @@
             self.tailNumber = None
 
-    def __repr__(self):
-        """Get the representation of the plane object."""
-        s = "<Plane: %s %s" % (self.tailNumber,
-                               "home" if self.status==const.PLANE_HOME else \
-                               "away" if self.status==const.PLANE_AWAY else \
-                               "parking" if self.status==const.PLANE_PARKING \
-                               else "unknown")
-        if self.gateNumber is not None:
-            s += " (gate " + self.gateNumber + ")"
-        s += ">"
-        return s
-
-
-#------------------------------------------------------------------------------
-
-class Fleet(object):
+#------------------------------------------------------------------------------
+
+class Fleet(rpccommon.Fleet):
     """Information about the whole fleet."""
     def __init__(self, f):
         """Construct the fleet information by reading the given file object."""
-        self._planes = {}
+        super(Fleet, self).__init__()
+
         while True:
             line = readline(f)
@@ -306,47 +298,5 @@
 
             plane = Plane(line)
-            if plane.tailNumber is not None:
-                self._planes[plane.tailNumber] = plane
-
-    def isGateConflicting(self, plane):
-        """Check if the gate of the given plane conflicts with another plane's
-        position."""
-        for p in self._planes.itervalues():
-            if p.tailNumber!=plane.tailNumber and \
-               p.status==const.PLANE_HOME and \
-               p.gateNumber==plane.gateNumber:
-                return True
-
-        return False
-
-    def getOccupiedGateNumbers(self):
-        """Get a set containing the numbers of the gates occupied by planes."""
-        gateNumbers = set()
-        for p in self._planes.itervalues():
-            if p.status==const.PLANE_HOME and p.gateNumber:
-                gateNumbers.add(p.gateNumber)
-        return gateNumbers
-
-    def updatePlane(self, tailNumber, status, gateNumber = None):
-        """Update the status of the given plane."""
-        if tailNumber in self._planes:
-            plane = self._planes[tailNumber]
-            plane.status = status
-            plane.gateNumber = gateNumber
-
-    def __iter__(self):
-        """Get an iterator over the planes."""
-        for plane in self._planes.itervalues():
-            yield plane
-
-    def __getitem__(self, tailNumber):
-        """Get the plane with the given tail number.
-
-        If the plane is not in the fleet, None is returned."""
-        return self._planes[tailNumber] if tailNumber in self._planes else None
-
-    def __repr__(self):
-        """Get the representation of the fleet object."""
-        return self._planes.__repr__()
+            self._addPlane(plane)
 
 #------------------------------------------------------------------------------
@@ -663,9 +613,51 @@
 #------------------------------------------------------------------------------
 
+class RPCRequest(Request):
+    """Common base class for RPC requests.
+
+    It stores the RPC client received from the handler."""
+    def __init__(self, client, callback):
+        """Construct the request."""
+        super(RPCRequest, self).__init__(callback)
+        self._client = client
+
+#------------------------------------------------------------------------------
+
+class Register(RPCRequest):
+    """A registration request."""
+    def __init__(self, client, callback, registrationData):
+        """Construct the request."""
+        super(Register, self).__init__(client, callback)
+        self._registrationData = registrationData
+
+    def run(self):
+        """Perform the registration."""
+
+        registrationData = self._registrationData
+
+        (resultCode, pilotID) = self._client.register(registrationData)
+        result = Result()
+        result.registered = resultCode==rpc.Client.RESULT_OK
+        if result.registered:
+            result.pilotID = pilotID
+
+            self._client.setCredentials(pilotID, registrationData.password)
+            loginResult = self._client.login()
+            result.loggedIn = loginResult is not None
+
+        result.invalidData = \
+          resultCode==rpc.Client.RESULT_INVALID_DATA
+        result.emailAlreadyRegistered = \
+          resultCode==rpc.Client.RESULT_EMAIL_ALREADY_REGISTERED
+
+        return result
+
+#------------------------------------------------------------------------------
+
 class Login(Request):
     """A login request."""
     iso88592decoder = codecs.getdecoder("iso-8859-2")
 
-    def __init__(self, callback, pilotID, password, entranceExam):
+    def __init__(self, callback, pilotID, password):
         """Construct the login request with the given pilot ID and
         password."""
@@ -674,5 +666,4 @@
         self._pilotID = pilotID
         self._password = password
-        self._entranceExam = entranceExam
 
     def run(self):
@@ -686,43 +677,29 @@
         password = md5.hexdigest()
 
-        if self._entranceExam:
-            url = "http://www.virtualairlines.hu/ellenorzo/getflightplan.php?pid=%s" % \
-                  (pilotID,)
-        else:
-            url = "http://www.virtualairlines.hu/leker2.php?pid=%s&psw=%s" % \
-                  (pilotID, password)
+        url = MAVA_BASE_URL + "/leker2.php?pid=%s&psw=%s" %  (pilotID, password)
 
         result = Result()
-        result.entranceExam = self._entranceExam
 
         f = urllib2.urlopen(url, timeout = 10.0)
 
         status = readline(f)
-        if self._entranceExam:
-            result.loggedIn = status != "#NOEXAM"
-        else:
-            result.loggedIn = status == ".OK."
+        result.loggedIn = status == ".OK."
 
         if result.loggedIn:
             result.pilotID = self._pilotID
             result.password = self._password
+            result.rank = "FO"
             result.flights = []
-            # FIXME: this may not be the correct behaviour
-            # for an entrance exam, but the website returns
-            # an error
-            if self._entranceExam:
-                result.pilotName = result.pilotID
-                result.exams = ""
-            else:
-                result.pilotName = self.iso88592decoder(readline(f))[0]
-                result.exams = readline(f)
-
-                while True:
-                    line = readline(f)
-                    if not line or line == "#ENDPIREP": break
-
-                    flight = BookedFlight(line)
-                    flight.readFromWeb(f)
-                    result.flights.append(flight)
+
+            result.pilotName = self.iso88592decoder(readline(f))[0]
+            result.exams = readline(f)
+
+            while True:
+                line = readline(f)
+                if not line or line == "#ENDPIREP": break
+
+                flight = BookedFlight(line)
+                flight.readFromWeb(f)
+                result.flights.append(flight)
 
             result.flights.sort(cmp = lambda flight1, flight2:
@@ -736,4 +713,57 @@
 #------------------------------------------------------------------------------
 
+class LoginRPC(RPCRequest):
+    """An RPC-based login request."""
+    def __init__(self, client, callback, pilotID, password):
+        """Construct the login request with the given pilot ID and
+        password."""
+        super(LoginRPC, self).__init__(client, callback)
+
+        self._pilotID = pilotID
+        self._password = password
+
+    def run(self):
+        """Perform the login request."""
+        result = Result()
+
+        self._client.setCredentials(self._pilotID, self._password)
+        loginResult = self._client.login()
+        result.loggedIn = loginResult is not None
+        if result.loggedIn:
+            result.pilotID = self._pilotID
+            result.pilotName = loginResult[0]
+            result.rank = loginResult[1]
+            result.password = self._password
+            result.flights = self._client.getFlights()
+            if result.rank=="STU":
+                reply = self._client.getEntryExamStatus()
+                result.entryExamPassed = reply[0]
+                result.entryExamLink = reply[1]
+                result.checkFlightStatus = reply[2]
+
+        return result
+
+#------------------------------------------------------------------------------
+
+class GetEntryExamStatus(RPCRequest):
+    """A request to get the entry exam status."""
+    def __init__(self, client, callback):
+        """Construct the request."""
+        super(GetEntryExamStatus, self).__init__(client, callback)
+
+    def run(self):
+        """Perform the query."""
+        result = Result()
+
+        reply = self._client.getEntryExamStatus()
+
+        result.entryExamPassed = reply[0]
+        result.entryExamLink = reply[1]
+        result.checkFlightStatus = reply[2]
+
+        return result
+
+#------------------------------------------------------------------------------
+
 class GetFleet(Request):
     """Request to get the fleet from the website."""
@@ -745,5 +775,5 @@
     def run(self):
         """Perform the login request."""
-        url = "http://www.virtualairlines.hu/onlinegates_get.php"
+        url = MAVA_BASE_URL + "/onlinegates_get.php"
 
         f = urllib2.urlopen(url, timeout = 10.0)
@@ -751,4 +781,20 @@
         result.fleet = Fleet(f)
         f.close()
+
+        return result
+
+#------------------------------------------------------------------------------
+
+class GetFleetRPC(RPCRequest):
+    """Request to get the fleet from the website using RPC."""
+    def __init__(self, client, callback):
+        """Construct the request with the given client and callback function."""
+        super(GetFleetRPC, self).__init__(client, callback)
+
+    def run(self):
+        """Perform the login request."""
+        result = Result()
+
+        result.fleet = self._client.getFleet()
 
         return result
@@ -767,9 +813,7 @@
     def run(self):
         """Perform the plane update."""
-        url = "http://www.virtualairlines.hu/onlinegates_set.php"
-
-        status = "H" if self._status==const.PLANE_HOME else \
-                 "A" if self._status==const.PLANE_AWAY else \
-                 "P" if self._status==const.PLANE_PARKING else ""
+        url = MAVA_BASE_URL + "/onlinegates_set.php"
+
+        status = Plane.status2str(self._status)
 
         gateNumber = self._gateNumber if self._gateNumber else ""
@@ -784,4 +828,26 @@
         result = Result()
         result.success = line == "OK"
+
+        return result
+
+#------------------------------------------------------------------------------
+
+class UpdatePlaneRPC(RPCRequest):
+    """RPC request to update the status and the position of a plane in the
+    fleet."""
+    def __init__(self, client, callback, tailNumber, status, gateNumber = None):
+        """Construct the request."""
+        super(UpdatePlaneRPC, self).__init__(client, callback)
+        self._tailNumber = tailNumber
+        self._status = status
+        self._gateNumber = gateNumber
+
+    def run(self):
+        """Perform the plane update."""
+        self._client.updatePlane(self._tailNumber, self._status, self._gateNumber)
+
+        # Otherwise an exception is thrown
+        result = Result()
+        result.success = True
 
         return result
@@ -898,9 +964,4 @@
 class SendPIREP(Request):
     """A request to send a PIREP to the MAVA website."""
-    _flightTypes = { const.FLIGHTTYPE_SCHEDULED : "SCHEDULED",
-                     const.FLIGHTTYPE_OLDTIMER : "OT",
-                     const.FLIGHTTYPE_VIP : "VIP",
-                     const.FLIGHTTYPE_CHARTER : "CHARTER" }
-
     _latin2Encoder = codecs.getencoder("iso-8859-2")
 
@@ -912,6 +973,5 @@
     def run(self):
         """Perform the sending of the PIREP."""
-        url = "http://www.virtualairlines.hu/malevacars.php"
-        #url = "http://localhost:15000"
+        url = MAVA_BASE_URL + "/malevacars.php"
 
         pirep = self._pirep
@@ -932,5 +992,5 @@
         data["mail"] = str(pirep.mailWeight)
 
-        data["flttype"] = SendPIREP._flightTypes[pirep.flightType]
+        data["flttype"] = pirep.flightTypeText
         data["onoff"] = "1" if pirep.online else "0"
         data["bt_dep"] = util.getTimestampString(pirep.blockTimeStart)
@@ -980,4 +1040,26 @@
 #------------------------------------------------------------------------------
 
+class SendPIREPRPC(RPCRequest):
+    """A request to send a PIREP to the MAVA website via the RPC interface."""
+
+    def __init__(self, client, callback, pirep):
+        """Construct the sending of the PIREP."""
+        super(SendPIREPRPC, self).__init__(client, callback)
+        self._pirep = pirep
+
+    def run(self):
+        """Perform the sending of the PIREP."""
+        pirep = self._pirep
+        resultCode = self._client.addPIREP(pirep.bookedFlight.id, pirep)
+
+        result = Result()
+        result.success = resultCode==rpc.Client.RESULT_OK
+        result.alreadyFlown = resultCode==rpc.Client.RESULT_FLIGHT_ALREADY_REPORTED
+        result.notAvailable = resultCode==rpc.Client.RESULT_FLIGHT_NOT_EXISTS
+
+        return result
+
+#------------------------------------------------------------------------------
+
 class SendACARS(Request):
     """A request to send an ACARS to the MAVA website."""
@@ -993,5 +1075,5 @@
         print "Sending the online ACARS"
 
-        url = "http://www.virtualairlines.hu/acars2/acarsonline.php"
+        url = MAVA_BASE_URL  + "/acars2/acarsonline.php"
 
         acars = self._acars
@@ -1027,4 +1109,20 @@
 #------------------------------------------------------------------------------
 
+class SendACARSRPC(RPCRequest):
+    """A request to send an ACARS to the MAVA website via JSON-RPC."""
+    def __init__(self, client, callback, acars):
+        """Construct the request for the given PIREP."""
+        super(SendACARSRPC, self).__init__(client, callback)
+        self._acars = acars
+
+    def run(self):
+        """Perform the sending of the ACARS."""
+        print "Sending the online ACARS via JSON-RPC"
+
+        self._client.updateOnlineACARS(self._acars)
+        return Result()
+
+#------------------------------------------------------------------------------
+
 class SendBugReport(Request):
     """A request to send a bug report to the project homepage."""
@@ -1063,5 +1161,5 @@
     It can process one request at a time. The results are passed to a callback
     function."""
-    def __init__(self):
+    def __init__(self, config, getCredentialsFn):
         """Construct the handler."""
         super(Handler, self).__init__()
@@ -1071,16 +1169,40 @@
 
         self.daemon = True
-
-    def login(self, callback, pilotID, password, entranceExam = False):
+        self._config = config
+        self._rpcClient = rpc.Client(getCredentialsFn)
+        if config.rememberPassword:
+            self._rpcClient.setCredentials(config.pilotID, config.password)
+
+    def register(self, callback, registrationData):
+        """Enqueue a registration request."""
+        self._addRequest(Register(self._rpcClient, callback, registrationData))
+
+    def login(self, callback, pilotID, password):
         """Enqueue a login request."""
-        self._addRequest(Login(callback, pilotID, password, entranceExam))
+        request = \
+          LoginRPC(self._rpcClient, callback, pilotID, password) \
+          if self._config.useRPC else Login(callback, pilotID, password)
+
+        self._addRequest(request)
+
+    def getEntryExamStatus(self, callback):
+        """Get the entry exam status."""
+        self._addRequest(GetEntryExamStatus(self._rpcClient, callback))
 
     def getFleet(self, callback):
         """Enqueue a fleet retrieval request."""
-        self._addRequest(GetFleet(callback))
+        request = \
+          GetFleetRPC(self._rpcClient, callback,) if self._config.useRPC \
+          else GetFleet(callback)
+        self._addRequest(request)
 
     def updatePlane(self, callback, tailNumber, status, gateNumber = None):
         """Update the status of the given plane."""
-        self._addRequest(UpdatePlane(callback, tailNumber, status, gateNumber))
+        request = \
+          UpdatePlaneRPC(self._rpcClient, callback,
+                         tailNumber, status, gateNumber) \
+          if self._config.useRPC \
+          else UpdatePlane(callback, tailNumber, status, gateNumber)
+        self._addRequest(request)
 
     def getNOTAMs(self, callback, departureICAO, arrivalICAO):
@@ -1094,9 +1216,15 @@
     def sendPIREP(self, callback, pirep):
         """Send the given PIREP."""
-        self._addRequest(SendPIREP(callback, pirep))
+        request = \
+          SendPIREPRPC(self._rpcClient, callback, pirep) if self._config.useRPC \
+          else SendPIREP(callback, pirep)
+        self._addRequest(request)
 
     def sendACARS(self, callback, acars):
         """Send the given ACARS"""
-        self._addRequest(SendACARS(callback, acars))
+        request = \
+          SendACARSRPC(self._rpcClient, callback, acars) if self._config.useRPC \
+          else SendACARS(callback, acars)
+        self._addRequest(request)
 
     def sendBugReport(self, callback, summary, description, email):
