Esempio n. 1
0
 def test_label_controller(self):
     """Make sure that ChamplainLabels are behaving."""
     gui.open_files(DEMOFILES)
     for photo in photos.values():
         self.assertEqual(photo.label.get_scale(), (1, 1))
         photo.label.emit("enter-event", Clutter.Event())
         self.assertEqual(photo.label.get_scale(), (1.05, 1.05))
         photo.label.emit("leave-event", Clutter.Event())
         self.assertEqual(photo.label.get_scale(), (1, 1))
         
         # Are Labels clickable?
         photo.label.emit("button-press", Clutter.Event())
         for button in ('save', 'revert', 'apply', 'close'):
             self.assertTrue(get_obj(button + '_button').get_sensitive())
         self.assertTrue(gui.labels.selection.iter_is_selected(photo.iter))
         self.assertEqual(gui.labels.selection.count_selected_rows(), 1)
         self.assertTrue(photo in selected)
         self.assertEqual(len(selected), 1)
         self.assertEqual(photo.label.get_scale(), (1.1, 1.1))
         self.assertTrue(photo.label.get_selected())
         self.assertEqual(photo.label.get_property('opacity'), 255)
         
         # Make sure the Labels that we didn't click on are deselected.
         for other in photos.values():
             if other.filename == photo.filename: continue
             self.assertFalse(gui.labels.selection.iter_is_selected(other.iter))
             self.assertFalse(other in selected)
             self.assertEqual(other.label.get_scale(), (1, 1))
             self.assertFalse(other.label.get_selected())
             self.assertEqual(other.label.get_property('opacity'), 64)
Esempio n. 2
0
 def test_label_controller(self):
     """Make sure that ChamplainLabels are behaving."""
     gui.open_files(DEMOFILES)
     for photo in photos.values():
         self.assertEqual(photo.label.get_scale(), (1, 1))
         photo.label.emit("enter-event", Clutter.Event())
         self.assertEqual(photo.label.get_scale(), (1.05, 1.05))
         photo.label.emit("leave-event", Clutter.Event())
         self.assertEqual(photo.label.get_scale(), (1, 1))
         
         # Are Labels clickable?
         photo.label.emit("button-press", Clutter.Event())
         for button in ('save', 'revert', 'close'):
             self.assertTrue(get_obj(button + '_button').get_sensitive())
         self.assertTrue(gui.labels.selection.iter_is_selected(photo.iter))
         self.assertEqual(gui.labels.selection.count_selected_rows(), 1)
         self.assertTrue(photo in selected)
         self.assertEqual(len(selected), 1)
         self.assertEqual(photo.label.get_scale(), (1.1, 1.1))
         self.assertTrue(photo.label.get_selected())
         self.assertEqual(photo.label.get_property('opacity'), 255)
         
         # Make sure the Labels that we didn't click on are deselected.
         for other in photos.values():
             if other.filename == photo.filename: continue
             self.assertFalse(gui.labels.selection.iter_is_selected(other.iter))
             self.assertFalse(other in selected)
             self.assertEqual(other.label.get_scale(), (1, 1))
             self.assertFalse(other.label.get_selected())
             self.assertEqual(other.label.get_property('opacity'), 64)
Esempio n. 3
0
 def test_drag_controller(self):
     """Make sure that we can drag photos around."""
     # Can we load files?
     data = Struct({'get_text': lambda: '\n'.join(DEMOFILES)})
     self.assertEqual(len(photos), 0)
     self.assertEqual(len(points), 0)
     gui.drag.photo_drag_end(None, None, 20, 20, data,
                             None, None, True)
     self.assertEqual(len(photos), 6)
     self.assertEqual(len(points), 374)
     for button in ('select_all', 'close', 'clear'):
         get_obj(button + '_button').emit('clicked')
     self.assertEqual(len(photos), 0)
     self.assertEqual(len(points), 0)
     
     gui.open_files(DEMOFILES)
     for photo in photos.values():
         # 'Drag' a ChamplainLabel and make sure the photo location matches.
         photo.label.set_location(random_coord(80), random_coord(180))
         photo.label.emit('drag-finish', Clutter.Event())
         self.assertEqual(photo.label.get_latitude(), photo.latitude)
         self.assertEqual(photo.label.get_longitude(), photo.longitude)
         self.assertGreater(len(photo.pretty_geoname()), 5)
         old = [photo.latitude, photo.longitude]
         
         # 'Drag' a photo onto the map and make sure that also works.
         selected.add(photo)
         data = Struct({'get_text': lambda: photo.filename})
         gui.drag.photo_drag_end(None, None, 20, 20, data,
                                 None, None, True)
         self.assertEqual(photo.label.get_latitude(), photo.latitude)
         self.assertEqual(photo.label.get_longitude(), photo.longitude)
         self.assertGreater(len(photo.pretty_geoname()), 5)
         self.assertNotEqual(photo.latitude, old[0])
         self.assertNotEqual(photo.longitude, old[1])
Esempio n. 4
0
 def test_drag_controller(self):
     """Make sure that we can drag photos around."""
     # Can we load files?
     data = Struct({'get_text': lambda: '\n'.join(DEMOFILES)})
     self.assertEqual(len(photos), 0)
     self.assertEqual(len(points), 0)
     gui.drag.photo_drag_end(None, None, 20, 20, data,
                             None, None, True)
     self.assertEqual(len(photos), 6)
     self.assertEqual(len(points), 374)
     self.tearDown()
     self.assertEqual(len(photos), 0)
     self.assertEqual(len(points), 0)
     
     gui.open_files(DEMOFILES)
     for photo in photos.values():
         # 'Drag' a ChamplainLabel and make sure the photo location matches.
         photo.label.set_location(random_coord(80), random_coord(180))
         photo.label.emit('drag-finish', Clutter.Event())
         self.assertEqual(photo.label.get_latitude(), photo.latitude)
         self.assertEqual(photo.label.get_longitude(), photo.longitude)
         self.assertGreater(len(photo.pretty_geoname()), 5)
         old = [photo.latitude, photo.longitude]
         
         # 'Drag' a photo onto the map and make sure that also works.
         selected.add(photo)
         data = Struct({'get_text': lambda: photo.filename})
         gui.drag.photo_drag_end(None, None, 20, 20, data,
                                 None, None, True)
         self.assertEqual(photo.label.get_latitude(), photo.latitude)
         self.assertEqual(photo.label.get_longitude(), photo.longitude)
         self.assertGreater(len(photo.pretty_geoname()), 5)
         self.assertNotEqual(photo.latitude, old[0])
         self.assertNotEqual(photo.longitude, old[1])
Esempio n. 5
0
 def test_timezone_lookups(self):
     """Ensure that the timezone can be discovered from the map."""
     # Be very careful to reset everything so that we're sure that
     # we're not just finding the timezone from gsettings.
     gst = GSettings('camera', 'canon_canon_powershot_a590_is')
     gst.reset('found-timezone')
     gst.reset('offset')
     gst.set_string('timezone-method', 'lookup')
     known_cameras.clear()
     
     # Open just the GPX
     gui.open_files([DEMOFILES[3]])
     
     # At this point the camera hasn't been informed of the timezone
     self.assertEqual(gst.get_string('found-timezone'), '')
     
     # Opening a photo should place it on the map.
     gui.open_files([DEMOFILES[0]])
     self.assertEqual(gst.get_string('found-timezone'), 'America/Edmonton')
     photo = photos.values()[0]
     self.assertAlmostEqual(photo.latitude, 53.530476, 5)
     self.assertAlmostEqual(photo.longitude, -113.450635, 5)
     
     # Manually specify the timezone to be not-Edmonton and confirm that
     # the photo clamps to the end of the gpx track.
     gst.set_string('timezone-method', 'custom')
     gst.set_int('timezone-region', 1)
     gst.set_int('timezone-cities', 43)
     self.assertAlmostEqual(photo.latitude, 53.52263, 5)
     self.assertAlmostEqual(photo.longitude, -113.44898, 5)
Esempio n. 6
0
def update_highlights(selection):
    """Ensure only the selected labels are highlighted."""
    selection_exists = selection.count_selected_rows() > 0
    selected.clear()
    for photo in photos.values():
        # Maintain the 'selected' set() for easier iterating later.
        if selection.iter_is_selected(photo.iter):
            selected.add(photo)
        photo.set_label_highlight(photo in selected, selection_exists)
Esempio n. 7
0
def update_highlights(selection):
    """Ensure only the selected labels are highlighted."""
    selection_exists = selection.count_selected_rows() > 0
    selected.clear()
    for photo in photos.values():
        # Maintain the 'selected' set() for easier iterating later.
        if selection.iter_is_selected(photo.iter):
            selected.add(photo)
        photo.set_label_highlight(photo in selected, selection_exists)
Esempio n. 8
0
 def apply_selected_photos(self, button):
     """Manually apply map center coordinates to all unpositioned photos."""
     for photo in photos.values():
         if photo.manual:
             continue
         photo.manual = True
         photo.set_location(
             map_view.get_property('latitude'),
             map_view.get_property('longitude'))
     self.labels.selection.emit('changed')
Esempio n. 9
0
 def time_offset_changed(self, widget):
     """Update all photos each time the camera's clock is corrected."""
     seconds = self.secbutton.get_value()
     minutes = self.minbutton.get_value()
     offset = int((minutes * 60) + seconds)
     if offset != metadata.delta:
         metadata.delta = offset
         if abs(seconds) == 60 and abs(minutes) != 60:
             minutes += seconds / 60
             self.secbutton.set_value(0)
             self.minbutton.set_value(minutes)
         for photo in photos.values():
             auto_timestamp_comparison(photo)
Esempio n. 10
0
 def time_offset_changed(self, widget):
     """Update all photos each time the camera's clock is corrected."""
     seconds = self.secbutton.get_value()
     minutes = self.minbutton.get_value()
     offset  = int((minutes * 60) + seconds)
     if offset != metadata.delta:
         metadata.delta = offset
         if abs(seconds) == 60 and abs(minutes) != 60:
             minutes += seconds / 60
             self.secbutton.set_value(0)
             self.minbutton.set_value(minutes)
         for photo in photos.values():
             auto_timestamp_comparison(photo)
Esempio n. 11
0
 def tearDown(self):
     """Undo whatever mess the testsuite created."""
     clear_all_gpx()
     for camera in known_cameras.values():
         camera.photos.clear()
     for photo in photos.values():
         gui.labels.layer.remove_marker(photo.label)
     photos.clear()
     modified.clear()
     selected.clear()
     gui.liststore.clear()
     system('git checkout demo')
     for key in app.gst.list_keys():
         app.gst.reset(key)
Esempio n. 12
0
 def test_camera_offsets(self):
     """Make sure that camera offsets function correctly."""
     gui.open_files([DEMOFILES[1]])
     spinbutton = known_cameras.values()[0].offset
     photo = photos.values()[0]
     for delta in (1, 10, 100, 600, -711):
         start = [photo.timestamp, spinbutton.get_value(),
                  photo.camera.gst.get_int('offset')]
         spinbutton.set_value(start[1] + delta)
         end = [photo.timestamp, spinbutton.get_value(),
                photo.camera.gst.get_int('offset')]
         # Check that the photo timestamp, spinbutton value, and gsettings
         # key have all changed by precisely the same amount.
         for i, num in enumerate(start):
             self.assertEqual(end[i] - num, delta)
Esempio n. 13
0
 def set_timezone(self):
     """Set the timezone to the given zone and update all photos."""
     if 'TZ' in environ:
         del environ['TZ']
     if gst.get_boolean('lookup-timezone'):
         environ['TZ'] = self.gpx_timezone
     elif gst.get_boolean('custom-timezone'):
         region = self.region.get_active_id()
         city   = self.cities.get_active_id()
         if region is not None and city is not None:
             environ['TZ'] = '%s/%s' % (region, city)
     tzset()
     for photo in photos.values():
         photo.calculate_timestamp()
         auto_timestamp_comparison(photo)
Esempio n. 14
0
 def test_demo_data(self):
     """Load the demo data and ensure that we're reading it in properly."""
     
     # Start with a fresh state.
     system('git checkout demo')
     self.assertEqual(len(points), 0)
     self.assertEqual(len(polygons), 0)
     self.assertEqual(app.metadata.alpha, float('inf'))
     self.assertEqual(app.metadata.omega, float('-inf'))
     
     # No buttons should be sensitive yet because nothing's loaded.
     buttons = {}
     for button in ('save', 'revert', 'apply', 'close', 'clear'):
         buttons[button] = get_obj(button + '_button')
         self.assertFalse(buttons[button].get_sensitive())
     
     # Load only the photos first.
     for filename in DEMOFILES:
         if filename[-3:] != 'gpx':
             self.assertRaises(IOError, gui.load_gpx_from_file, filename)
             gui.load_img_from_file(filename)
     
     # Nothing is yet selected or modified, so buttons still insensitive.
     for button in buttons.values():
         self.assertFalse(button.get_sensitive())
     
     # Something loaded in the liststore?
     self.assertEqual(len(gui.liststore), 6)
     self.assertTrue(gui.liststore.get_iter_first())
     
     for photo in photos.values():
         self.assertFalse(photo in modified)
         self.assertFalse(photo in selected)
         self.assertFalse(photo.label.get_property('visible'))
         
         # Pristine demo data shouldn't have any tags.
         self.assertIsNone(photo.altitude)
         self.assertIsNone(photo.latitude)
         self.assertIsNone(photo.longitude)
         self.assertFalse(photo.manual)
         
         # Test that missing the provincestate doesn't break the geoname.
         photo.latitude = 47.56494
         photo.longitude = -52.70931
         photo.lookup_geoname()
         self.assertEqual(photo.pretty_geoname(),
             "St. John's,\nNewfoundland and Labrador,\nCanada")
         photo.set_geodata(['Anytown', None, 'US', 'timezone'])
         self.assertEqual(photo.pretty_geoname(), 'Anytown, United States')
         self.assertEqual(photo.timezone, 'timezone')
         
         # Add some crap
         photo.manual    = True
         photo.latitude  = 10.0
         photo.altitude  = 650
         photo.longitude = 45.0
         self.assertTrue(photo.valid_coords())
         
         # photo.read() should discard all the crap we added above.
         # This is in response to a bug where I was using pyexiv2 wrongly
         # and it would load data from disk without discarding old data.
         photo.read()
         self.assertEqual(photo.pretty_geoname(), '')
         self.assertIsNone(photo.altitude)
         self.assertIsNone(photo.latitude)
         self.assertIsNone(photo.longitude)
         self.assertFalse(photo.valid_coords())
         self.assertFalse(photo.manual)
         self.assertEqual(photo.filename, photo.label.get_name())
         self.assertEqual(photo.timestamp,
             gui.liststore.get_value(photo.iter, app.TIMESTAMP))
     
     # Test the select-all button.
     select_all = get_obj('select_all_button')
     select_all.set_active(True)
     self.assertEqual(len(selected), len(gui.liststore))
     select_all.set_active(False)
     self.assertEqual(len(selected), 0)
     
     # Load the GPX
     gpx_filename=join(PKG_DATA_DIR, '..', 'demo', '20101016.gpx')
     self.assertRaises(IOError, gui.load_img_from_file, gpx_filename)
     gui.load_gpx_from_file(gpx_filename)
     self.assertTrue(buttons['clear'].get_sensitive())
     gui.labels.selection.emit('changed')
     
     # Check that the GPX is loaded
     self.assertEqual(len(points), 374)
     self.assertEqual(len(polygons), 1)
     self.assertEqual(app.metadata.alpha, 1287259751)
     self.assertEqual(app.metadata.omega, 1287260756)
     
     # The save button should be sensitive because loading GPX modifies
     # photos, but nothing is selected so the others are insensitive.
     self.assertTrue(buttons['save'].get_sensitive())
     for button in ('revert', 'apply', 'close'):
         self.assertFalse(buttons[button].get_sensitive())
     
     for photo in photos.values():
         self.assertTrue(photo in modified)
         
         self.assertIsNotNone(photo.latitude)
         self.assertIsNotNone(photo.longitude)
         self.assertTrue(photo.valid_coords())
         self.assertTrue(photo.label.get_property('visible'))
     
     # Unload the GPX data.
     buttons['clear'].emit('clicked')
     self.assertEqual(len(points), 0)
     self.assertEqual(len(polygons), 0)
     self.assertFalse(buttons['clear'].get_sensitive())
     
     # Save all photos
     buttons['save'].emit('clicked')
     self.assertEqual(len(modified), 0)
     for button in ('save', 'revert'):
         self.assertFalse(buttons[button].get_sensitive())
     
     gui.labels.selection.select_all()
     self.assertEqual(len(selected), 6)
     for button in ('save', 'revert'):
         self.assertFalse(buttons[button].get_sensitive())
     for button in ('apply', 'close'):
         self.assertTrue(buttons[button].get_sensitive())
     
     # Close all the photos.
     files = [photo.filename for photo in selected]
     buttons['close'].emit('clicked')
     for button in ('save', 'revert', 'apply', 'close'):
         self.assertFalse(buttons[button].get_sensitive())
     self.assertEqual(len(photos), 0)
     self.assertEqual(len(modified), 0)
     self.assertEqual(len(selected), 0)
     
     # Re-read the photos back from disk to make sure that the saving
     # was successful.
     for filename in files:
         photo = Photograph(filename, lambda x: None)
         photo.read()
         self.assertTrue(photo.valid_coords())
         self.assertGreater(photo.altitude, 600)
         self.assertEqual(photo.pretty_geoname(), 'Edmonton, Alberta, Canada')
Esempio n. 15
0
 def __init__(self):
     self.message_timeout_source = None
     self.progressbar = get_obj('progressbar')
     
     self.error = Struct({
         'message': get_obj('error_message'),
         'icon': get_obj('error_icon'),
         'bar': get_obj('error_bar')
     })
     
     self.error.bar.connect('response', lambda widget, signal: widget.hide())
     
     self.strings = Struct({
         'quit':    get_obj('quit').get_property('secondary-text'),
         'preview': get_obj('preview_label').get_text()
     })
     
     self.liststore = get_obj('loaded_photos')
     self.liststore.set_sort_column_id(TIMESTAMP, Gtk.SortType.ASCENDING)
     
     cell_string = Gtk.CellRendererText()
     cell_string.set_property('wrap-mode', Pango.WrapMode.WORD)
     cell_string.set_property('wrap-width', 200)
     cell_thumb  = Gtk.CellRendererPixbuf()
     cell_thumb.set_property('stock-id', Gtk.STOCK_MISSING_IMAGE)
     cell_thumb.set_property('ypad', 6)
     cell_thumb.set_property('xpad', 12)
     
     column = Gtk.TreeViewColumn('Photos')
     column.pack_start(cell_thumb, False)
     column.add_attribute(cell_thumb, 'pixbuf', THUMB)
     column.pack_start(cell_string, False)
     column.add_attribute(cell_string, 'markup', SUMMARY)
     column.set_sizing(Gtk.TreeViewColumnSizing.AUTOSIZE)
     
     # Deal with multiple selection drag and drop.
     self.defer_select = False
     photos_view = get_obj('photos_view')
     photos_view.connect('button-press-event', self.photoview_pressed)
     photos_view.connect('button-release-event', self.photoview_released)
     photos_view.append_column(column)
     
     self.drag      = DragController(self.open_files)
     self.navigator = NavigationController()
     self.search    = SearchController()
     self.labels    = LabelController()
     self.actors    = ActorController()
     
     about = get_obj('about')
     about.set_version(REVISION)
     about.set_program_name(APPNAME)
     about.set_logo(GdkPixbuf.Pixbuf.new_from_file_at_size(
         join(PKG_DATA_DIR, PACKAGE + '.svg'), 192, 192))
     
     click_handlers = {
         'open_button':
             [self.add_files_dialog, get_obj('open')],
         'save_button':
             [self.save_all_files],
         'close_button':
             [lambda btn: [p.destroy() for p in selected.copy()]],
         'revert_button':
             [lambda btn: self.open_files(
                 [p.filename for p in modified & selected])],
         'about_button':
             [lambda yes, you_can: you_can.run() and you_can.hide(), about],
         'help_button':
             [lambda *ignore: Gtk.show_uri(Gdk.Screen.get_default(),
                 'ghelp:gottengeography', Gdk.CURRENT_TIME)],
         'jump_button':
             [self.jump_to_photo],
         'apply_button':
             [self.apply_selected_photos],
     }
     for button, handler in click_handlers.items():
         get_obj(button).connect('clicked', *handler)
     
     # Hide the unused button that appears beside the map source menu.
     ugly = get_obj('map_source_menu_button').get_child().get_children()[0]
     ugly.set_no_show_all(True)
     ugly.hide()
     
     accel  = Gtk.AccelGroup()
     window = get_obj('main')
     window.resize(*gst.get('window-size'))
     window.connect('delete_event', self.confirm_quit_dialog)
     window.add_accel_group(accel)
     window.show_all()
     
     save_size = lambda v, s, size: gst.set_window_size(size())
     for prop in ['width', 'height']:
         map_view.connect('notify::' + prop, save_size, window.get_size)
     
     accel.connect(Gdk.keyval_from_name('q'),
         Gdk.ModifierType.CONTROL_MASK, 0, self.confirm_quit_dialog)
     
     self.labels.selection.emit('changed')
     clear_all_gpx()
     
     button = get_obj('apply_button')
     gst.bind('left-pane-page', get_obj('photo_camera_gps'), 'page')
     gst.bind('use-dark-theme', Gtk.Settings.get_default(),
              'gtk-application-prefer-dark-theme')
     
     # This bit of magic will only show the apply button when there is
     # at least one photo loaded that is not manually positioned.
     # In effect, it allows you to manually drag & drop some photos,
     # then batch-apply all the rest
     btn_sense = lambda *x: button.set_sensitive(
         [photo for photo in photos.values() if not photo.manual])
     self.liststore.connect('row-changed', btn_sense)
     self.liststore.connect('row-deleted', btn_sense)
     
     empty = get_obj('empty_photo_list')
     empty_visible = lambda l, *x: empty.set_visible(l.get_iter_first() is None)
     self.liststore.connect('row-changed', empty_visible)
     self.liststore.connect('row-deleted', empty_visible)
     
     toolbar = get_obj('photo_btn_bar')
     bar_visible = lambda l, *x: toolbar.set_visible(l.get_iter_first() is not None)
     self.liststore.connect('row-changed', bar_visible)
     self.liststore.connect('row-deleted', bar_visible)
     
     get_obj('open').connect('update-preview', self.update_preview,
         get_obj('preview_label'), get_obj('preview_image'))
Esempio n. 16
0
 def test_demo_data(self):
     """Load the demo data and ensure that we're reading it in properly."""
     self.assertEqual(len(points), 0)
     self.assertEqual(len(known_trackfiles), 0)
     self.assertEqual(app.metadata.alpha, float('inf'))
     self.assertEqual(app.metadata.omega, float('-inf'))
     
     # No buttons should be sensitive yet because nothing's loaded.
     buttons = {}
     for button in ('save', 'revert', 'close'):
         buttons[button] = get_obj(button + '_button')
         self.assertFalse(buttons[button].get_sensitive())
     
     # Load only the photos first.
     for filename in DEMOFILES:
         if filename[-3:] != 'gpx':
             self.assertRaises(IOError, gui.load_gpx_from_file, filename)
             gui.open_files([filename])
     
     # Nothing is yet selected or modified, so buttons still insensitive.
     for button in buttons.values():
         self.assertFalse(button.get_sensitive())
     
     # Something loaded in the liststore?
     self.assertEqual(len(gui.liststore), 6)
     self.assertTrue(gui.liststore.get_iter_first())
     
     for photo in photos.values():
         self.assertFalse(photo in modified)
         self.assertFalse(photo in selected)
         
         # Pristine demo data shouldn't have any tags.
         self.assertIsNone(photo.altitude)
         self.assertIsNone(photo.latitude)
         self.assertIsNone(photo.longitude)
         self.assertFalse(photo.manual)
         
         # Test that missing the provincestate doesn't break the geoname.
         photo.latitude = 47.56494
         photo.longitude = -52.70931
         photo.lookup_geoname()
         self.assertEqual(photo.pretty_geoname(),
             "St. John's, Newfoundland and Labrador, Canada")
         photo.set_geodata(['Anytown', None, 'US', 'timezone'])
         self.assertEqual(photo.pretty_geoname(), 'Anytown, United States')
         self.assertEqual(photo.timezone, 'timezone')
         
         # Add some crap
         photo.manual    = True
         photo.latitude  = 10.0
         photo.altitude  = 650
         photo.longitude = 45.0
         self.assertTrue(photo.valid_coords())
         
         # photo.read() should discard all the crap we added above.
         # This is in response to a bug where I was using pyexiv2 wrongly
         # and it would load data from disk without discarding old data.
         photo.read()
         self.assertEqual(photo.pretty_geoname(), '')
         self.assertIsNone(photo.altitude)
         self.assertIsNone(photo.latitude)
         self.assertIsNone(photo.longitude)
         self.assertFalse(photo.valid_coords())
         self.assertFalse(photo.manual)
         self.assertEqual(photo.filename, photo.label.get_name())
     
     # Load the GPX
     gpx_filename = join(PKG_DATA_DIR, '..', 'demo', '20101016.gpx')
     self.assertRaises(IOError, gui.load_img_from_file, gpx_filename)
     gui.open_files([gpx_filename])
     gui.labels.selection.emit('changed')
     
     # Check that the GPX is loaded
     self.assertEqual(len(points), 374)
     self.assertEqual(len(known_trackfiles), 1)
     self.assertEqual(app.metadata.alpha, 1287259751)
     self.assertEqual(app.metadata.omega, 1287260756)
     
     # The save button should be sensitive because loading GPX modifies
     # photos, but nothing is selected so the others are insensitive.
     self.assertTrue(buttons['save'].get_sensitive())
     for button in ('revert', 'close'):
         self.assertFalse(buttons[button].get_sensitive())
     
     for photo in photos.values():
         self.assertTrue(photo in modified)
         
         self.assertIsNotNone(photo.latitude)
         self.assertIsNotNone(photo.longitude)
         self.assertTrue(photo.valid_coords())
         self.assertTrue(photo.label.get_property('visible'))
     
     # Unload the GPX data.
     clear_all_gpx()
     self.assertEqual(len(points), 0)
     self.assertEqual(len(known_trackfiles), 0)
     
     # Save all photos
     buttons['save'].emit('clicked')
     self.assertEqual(len(modified), 0)
     for button in ('save', 'revert'):
         self.assertFalse(buttons[button].get_sensitive())
     
     gui.labels.selection.select_all()
     self.assertEqual(len(selected), 6)
     for button in ('save', 'revert'):
         self.assertFalse(buttons[button].get_sensitive())
     self.assertTrue(buttons['close'].get_sensitive())
     
     # Close all the photos.
     files = [photo.filename for photo in selected]
     buttons['close'].emit('clicked')
     for button in ('save', 'revert', 'close'):
         self.assertFalse(buttons[button].get_sensitive())
     self.assertEqual(len(photos), 0)
     self.assertEqual(len(modified), 0)
     self.assertEqual(len(selected), 0)
     
     # Re-read the photos back from disk to make sure that the saving
     # was successful.
     for filename in files:
         photo = Photograph(filename)
         photo.read()
         self.assertTrue(photo.valid_coords())
         self.assertGreater(photo.altitude, 600)
         self.assertEqual(photo.pretty_geoname(), 'Edmonton, Alberta, Canada')