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)
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)
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])
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])
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)
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)
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')
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)
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)
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)
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)
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')
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'))
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')