#!/usr/bin/python #-*- coding:utf-8 -*- from os import path import gc import gtk import gtk.glade import gobject import pango import data import xmldriver from areas import Areas import tagmap_globals as tg class TagMap( object ): def __init__( self ): self.marks = [] self.place = None self.bullet = gtk.gdk.pixbuf_new_from_file(path.join(tg.glade_dir, 'bullet.png')) self.banner = gtk.gdk.pixbuf_new_from_file(path.join(tg.glade_dir, 'banner.png')) mapfile, \ self.groundzero, \ self.scale, \ self.atlas = xmldriver.load_atlas(path.join(tg.data_dir, 'atlas.xml')) self.areas = Areas(self.groundzero, self.scale) self.zoom = 1.0 self.marks_filename = None self.last_directory = path.expanduser('~') self.change_cursor = None self.debug = False #pointer self.pointer = None self.pointer_timeout = 208 self.pointer_raio = 20 self.pointer_counter = -1 self.timeout = None for key in self.atlas.keys(): self.areas.add_area(key, self.atlas[key].area) # Glade --------------------------------------- wTree = gtk.glade.XML(path.join(tg.glade_dir, 'tagmap.glade')) self.window = wTree.get_widget('tmWindow') self.about = wTree.get_widget('tagmapAbout') self.about.set_name('TagMap') self.about.set_version(tg.version) self.trackdialog = wTree.get_widget('trackDialog') self.gotodialog = wTree.get_widget('gotoDialog') self.window.set_icon_from_file(path.join(tg.glade_dir, 'tagmap-icon.png')) self.about.set_icon_from_file(path.join(tg.glade_dir, 'tagmap-icon.png')) self.trackdialog.set_icon_from_file(path.join(tg.glade_dir, 'tagmap-icon.png')) self.gotodialog.set_icon_from_file(path.join(tg.glade_dir, 'tagmap-icon.png')) f = open(path.join(tg.doc_dir, 'COPYING')) license = f.read() f.close() if license: self.about.set_license(license) self.status = wTree.get_widget('statusbar') self.id = self.status.get_context_id('status') self.mapMenu = wTree.get_widget('mapMenu') self.notebook = wTree.get_widget('notebook') self.searchEntry = wTree.get_widget('searchEntry') self.horizontalCombo = wTree.get_widget('horizontalCombo') self.verticalCombo = wTree.get_widget('verticalCombo') self.horizontalSpin = wTree.get_widget('horizontalSpin') self.verticalSpin = wTree.get_widget('verticalSpin') self.zoomInBtn = wTree.get_widget('zoomInBtn') self.zoomOutBtn = wTree.get_widget('zoomOutBtn') self.track_menuitem = wTree.get_widget('track_menuitem') self.save_menuitem = wTree.get_widget('save_menuitem') self.saveas_menuitem = wTree.get_widget('saveas_menuitem') self.export_menuitem = wTree.get_widget('export_menuitem') self.clearmarks_menuitem = wTree.get_widget('clear_marks') self.removemark_menuitem = wTree.get_widget('remove_last_mark') self.enable_marks_items(False) self.textScrolledWindow = wTree.get_widget('textScrolledWindow') self.mapVp = wTree.get_widget('mapViewport') self.mapImg = wTree.get_widget('mapImage') self.map_pixbuf_orig = gtk.gdk.pixbuf_new_from_file(path.join(tg.data_dir, mapfile)) self.map_pixbuf = self.map_pixbuf_orig self.map_width = self.map_pixbuf_orig.props.width self.map_height = self.map_pixbuf_orig.props.height self.map_pixmap = gtk.gdk.Pixmap(None, self.map_width, self.map_pixbuf.props.height, gtk.gdk.visual_get_system().depth) self.gc = self.map_pixmap.new_gc() cmap = self.map_pixmap.get_colormap() self.gc.foreground = cmap.alloc_color('red') self.gc.set_line_attributes(2, gtk.gdk.LINE_SOLID, gtk.gdk.CAP_ROUND, gtk.gdk.JOIN_ROUND) # TreeView ------------------------------------ self.searchView = wTree.get_widget('searchList') column = gtk.TreeViewColumn('', gtk.CellRendererPixbuf(), pixbuf=1) #column.set_sort_column_id(0) self.searchView.append_column(column) column = gtk.TreeViewColumn('Local', gtk.CellRendererText(), text=2) #column.set_sort_column_id(2) self.searchView.append_column(column) self.icons = [gtk.gdk.pixbuf_new_from_file(path.join(tg.glade_dir, 'place.png')), gtk.gdk.pixbuf_new_from_file(path.join(tg.glade_dir, 'city.png')), gtk.gdk.pixbuf_new_from_file(path.join(tg.glade_dir, 'kingdom.png')), gtk.gdk.pixbuf_new_from_file(path.join(tg.glade_dir, 'region.png')), gtk.gdk.pixbuf_new_from_file(path.join(tg.glade_dir, 'forest.png')), gtk.gdk.pixbuf_new_from_file(path.join(tg.glade_dir, 'mountain.png')), gtk.gdk.pixbuf_new_from_file(path.join(tg.glade_dir, 'water.png')), gtk.gdk.pixbuf_new_from_file(path.join(tg.glade_dir, 'island.png'))] self.searchList = gtk.TreeStore(int, gtk.gdk.Pixbuf, str) self.searchList.set_sort_column_id(2, gtk.SORT_ASCENDING) self.searchFilter = self.searchList.filter_new() self.searchFilter.set_visible_func(self.searchVisible, data=None) self.searchView.set_model(self.searchFilter) for key in self.atlas.keys(): item = self.atlas[key] try: if item.kingdom: continue except: pass iter = self.searchList.append(None, [item.type, self.icons[item.type], key]) if item.type == item.KINGDOM: for place in item.places: self.searchList.append(iter, [item.type, self.icons[place.type], place.name]) self.atlas[place.name] = place self.searchView.expand_all() self.infoText = gtk.TextBuffer(None) self.descrView = wTree.get_widget('descrView') self.descrView.set_buffer(self.infoText) self.trackText = gtk.TextBuffer(None) wTree.get_widget('trackView').set_buffer(self.trackText) # Text Tags ----------------------------------- track_tags = self.trackText.get_tag_table() tag = gtk.TextTag('title') tag.set_property('pixels-below-lines', 4) tag.set_property('font', 'sans bold 18') tag.set_property('justification', gtk.JUSTIFY_CENTER) track_tags.add(tag) info_tags = self.infoText.get_tag_table() tag = gtk.TextTag('title1') tag.set_property('pixels-below-lines', 4) tag.set_property('font', 'sans bold 18') tag.set_property('justification', gtk.JUSTIFY_CENTER) info_tags.add(tag) tag = gtk.TextTag('title2') tag.set_property('pixels-below-lines', 4) tag.set_property('font', 'sans bold 13') tag.set_property('justification', gtk.JUSTIFY_CENTER) info_tags.add(tag) tag = gtk.TextTag('link') tag.set_property('weight', pango.WEIGHT_BOLD) tag.connect('event', self.hyperlink_handler) info_tags.add(tag) tag = gtk.TextTag('quote') tag.set_property('pixels-below-lines', 4) tag.set_property('font', 'sans 11') tag.set_property('style', pango.STYLE_ITALIC) info_tags.add(tag) tag = gtk.TextTag('author') tag.set_property('pixels-below-lines', 4) tag.set_property('font', 'sans 11') tag.set_property('justification', gtk.JUSTIFY_RIGHT) info_tags.add(tag) tag = gtk.TextTag('center') tag.set_property('justification', gtk.JUSTIFY_CENTER) info_tags.add(tag) # FileDialog ---------------------------------- self.mark_filter = gtk.FileFilter() self.mark_filter.set_name('Arquivo de Roteiro de Viagem (*.tgm)') self.mark_filter.add_pattern('*.tgm') # Drag-n-Drop --------------------------------- self.searchView.drag_source_set(gtk.gdk.BUTTON1_MASK, [('text/plain', gtk.TARGET_SAME_APP, 1)], gtk.gdk.ACTION_COPY) self.mapVp.drag_dest_set(gtk.DEST_DEFAULT_MOTION | gtk.DEST_DEFAULT_HIGHLIGHT | gtk.DEST_DEFAULT_DROP, [('text/plain', gtk.TARGET_SAME_APP, 1)], gtk.gdk.ACTION_DEFAULT | gtk.gdk.ACTION_COPY) self.mapVp.connect('drag-data-received', self.place_data_received) #GDK Events #Pointer Motion | Button 1 Motion | Button Release | Structure self.window.connect('destroy', gtk.main_quit) self.mapVp.connect('motion-notify-event', self.motion) wTree.signal_autoconnect(self) self.draw_map() # Callbacks ---------------------------------------------------------------- # Dialog callbacks ---------- def show_about(self, widget): self.about.show() def hide_about(self, widget, response_id): self.about.hide() return True def show_trackdialog(self, widget): if self.marks: self.trackText.set_text('') self.trackText.insert_with_tags_by_name(self.trackText.get_end_iter(), 'Roteiro de Viagem\n', 'title') for mark in self.humanize_marks(self.marks): self.trackText.insert_at_cursor(' ') self.trackText.insert_pixbuf(self.trackText.get_end_iter(), self.bullet) if mark[0] < 10: tabs = '\t\t' else: tabs = '\t' if mark[1]: place = ' - %s' % mark[1] else: place = '' self.trackText.insert_at_cursor('%dkm:%s%s%s\n' % \ (mark[0], tabs, mark[2], place)) self.trackText.insert_at_cursor( '\nObs: as coordenadas usam como referência o Domo de Arminus') self.trackdialog.show() def hide_trackdialog(self, widget, response_id): self.trackdialog.hide() return True def show_gotodialog(self, widget): self.horizontalCombo.set_active(0) self.verticalCombo.set_active(0) self.horizontalSpin.set_value(0) self.verticalSpin.set_value(0) self.gotodialog.show() def hide_gotodialog(self, widget, response_id): self.gotodialog.hide() if response_id == 0: x = self.horizontalSpin.get_value() y = self.verticalSpin.get_value() if self.horizontalCombo.get_active(): x = -x if self.verticalCombo.get_active(): y = -y self.goto_coord((x, y), True) return True # Other callbacks ----------- def debug_switch(self, widget): self.debug = not self.debug def drag_data_get_cb(self, treeview, context, selection, info, timestamp): treeselection = treeview.get_selection() model, iter = treeselection.get_selected() selection.set('text/plain', 8, model.get_value(iter, 2)) def place_data_received(self, widget, ct, x, y, selection, info, timestamp): self.place = selection.data self.goto_place(self.place) def quit(self, widget): gtk.main_quit() def show_places_tab(self, widget): self.notebook.set_current_page(0) def show_info_tab(self, widget): self.notebook.set_current_page(1) def motion(self, widget, event): coord = event.get_coords() border = (self.mapVp.allocation.width - self.map_width) / 2 if border > 0: coord = (coord[0] - border, coord[1]) coords = self.areas.revert_point(coord) self.place = self.areas.inside_area(coords) if self.place: self.status.push(self.id, self.place) elif self.debug: self.status.push(self.id, str(coords)) def press(self, widget, event): if event.type == gtk.gdk._2BUTTON_PRESS: coord = tuple([int(n) for n in event.get_coords()]) border = (self.mapVp.allocation.width - self.map_width) / 2 if border > 0: coord = (coord[0] - border, coord[1]) if not self.marks: self.enable_marks_items(True) self.marks.append((self.place, self.areas.revert_point(coord))) self.draw_map() elif event.type == gtk.gdk.BUTTON_PRESS: if event.button == 3: self.mapMenu.popup(None, None, None, event.button, event.time) else: if self.place: self.notebook.set_current_page(1) self.show_site() def expand_tree(self, widget): self.searchView.expand_all() def collapse_tree(self, widget): self.searchView.collapse_all() def search_changed(self, widget): self.searchFilter.refilter() def searchitem_selected(self, widget, path, column): self.place = self.searchFilter.get_value(self.searchFilter.get_iter(path), 2) self.goto_place(self.place) def hyperlink_handler(self, texttag, widget, event, iter): if event.type == gtk.gdk.BUTTON_PRESS: begin = iter.copy() while not begin.begins_tag(texttag): begin.backward_char() end = iter.copy() while not end.ends_tag(texttag): end.forward_char() self.place = self.infoText.get_text(begin, end).decode('utf-8').strip() self.goto_place(self.place) self.notebook.set_current_page(1) def zoom_in(self, widget): if self.zoom < 2.0: self.zoom = self.zoom * 2.0 if self.zoom == 2.0: self.zoomInBtn.set_sensitive(False) self.zoomOutBtn.set_sensitive(True) self.areas.scale = self.areas.scale * 2.0 self.areas.groundzero = tuple([n * 2 for n in self.areas.groundzero]) self.resize_map(2.0) def zoom_out(self, widget): if self.zoom > 0.25: self.zoom = self.zoom * 0.5 if self.zoom == 0.25: self.zoomOutBtn.set_sensitive(False) self.zoomInBtn.set_sensitive(True) self.areas.scale = self.areas.scale * 0.5 self.areas.groundzero = tuple([n / 2 for n in self.areas.groundzero]) self.resize_map(0.5) def zoom_normal(self, widget): if self.zoom != 1.0: zoom_factor = 1.0 / self.zoom self.zoomOutBtn.set_sensitive(True) self.zoomInBtn.set_sensitive(True) self.areas.groundzero = self.groundzero self.areas.scale = self.scale self.zoom = 1.0 self.resize_map(zoom_factor) def clear_marks(self, widget): #debug print for mark in self.marks: print '' % mark[1] #debug self.marks_filename = None if self.marks: self.enable_marks_items(False) self.marks = [] self.status.push(self.id, '') self.draw_map() def load_marks(self, widget): fileDlg = gtk.FileChooserDialog(title='Abrir arquivo de trilha...', parent=self.window, action=gtk.FILE_CHOOSER_ACTION_OPEN, buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OK, gtk.RESPONSE_OK)) fileDlg.set_current_folder(self.last_directory) fileDlg.add_filter(self.mark_filter) if fileDlg.run() == gtk.RESPONSE_OK: self.marks_filename = fileDlg.get_filename() self.last_directory, filename = path.split(self.marks_filename) self.marks = xmldriver.load_marks(self.marks_filename) self.enable_marks_items(True) self.draw_map() fileDlg.hide() fileDlg.destroy() def save_marks(self, widget): if self.marks_filename: self.save_marks_to_file() else: self.save_marks_as(widget, 'Salvar trilha') def save_marks_as(self, widget, title='Salvar trilha como...'): fileDlg = gtk.FileChooserDialog(title, parent=self.window, action=gtk.FILE_CHOOSER_ACTION_SAVE, buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OK, gtk.RESPONSE_OK)) fileDlg.set_do_overwrite_confirmation(True) fileDlg.set_current_folder(self.last_directory) fileDlg.add_filter(self.mark_filter) if fileDlg.run() == gtk.RESPONSE_OK: self.marks_filename = fileDlg.get_filename() ext = path.splitext(self.marks_filename)[1] if ext != '.tgm': self.marks_filename += '.tgm' self.last_directory, filename = path.split(self.marks_filename) self.save_marks_to_file() fileDlg.hide() fileDlg.destroy() def export_marks(self, widget): fileDlg = gtk.FileChooserDialog(title='Exportar trilha como HTML', parent=self.window, action=gtk.FILE_CHOOSER_ACTION_SAVE, buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OK, gtk.RESPONSE_OK)) fileDlg.set_do_overwrite_confirmation(True) fileDlg.set_current_folder(self.last_directory) html_filter = gtk.FileFilter() html_filter.set_name('Página HTML (*.html)') html_filter.add_pattern('*.html') fileDlg.add_filter(html_filter) if fileDlg.run() == gtk.RESPONSE_OK: html_filename = fileDlg.get_filename() ext = path.splitext(html_filename)[1] if ext != '.html' and ext != '.htm': html_filename += '.html' self.last_directory, filename = path.split(html_filename) xmldriver.export_marks_as_html(self.humanize_marks(self.marks), html_filename) fileDlg.hide() fileDlg.destroy() def remove_last_mark(self, widget): if self.marks: self.marks.pop() if not self.marks: self.enable_marks_items(False) self.draw_map() def show_info(self, widget): self.show_site() # Support Functions -------------------------------------------------------- def enable_marks_items(self, enabled=True): self.track_menuitem.set_sensitive(enabled) self.save_menuitem.set_sensitive(enabled) self.saveas_menuitem.set_sensitive(enabled) self.export_menuitem.set_sensitive(enabled) self.clearmarks_menuitem.set_sensitive(enabled) self.removemark_menuitem.set_sensitive(enabled) def show_site(self): try: site = self.atlas[self.place] except: return self.textScrolledWindow.get_vadjustment().set_value(0) self.infoText.set_text('') self.infoText.insert_with_tags_by_name(self.infoText.get_end_iter(), '%s\n' % site.name, 'title1') if site.image: self.infoText.insert_pixbuf(self.infoText.get_end_iter(), gtk.gdk.pixbuf_new_from_file(path.join(tg.data_dir, site.image))) if site.quote: self.infoText.insert_with_tags_by_name(self.infoText.get_end_iter(), '\n\n"%s"\n' % site.quote[0], 'quote') self.infoText.insert_with_tags_by_name(self.infoText.get_end_iter(), site.quote[1], 'author') self.infoText.insert_at_cursor('\n\n') if site.description: self.infoText.insert_at_cursor(' %s\n' % site.description) if site.type == site.KINGDOM and site.places: self.infoText.insert_with_tags_by_name(self.infoText.get_end_iter(), 'Cidades & Locais', 'title2') self.infoText.insert_at_cursor('\n\n') for place in site.places: self.infoText.insert_at_cursor(' ') self.infoText.insert_pixbuf(self.infoText.get_end_iter(), self.bullet) self.infoText.insert_with_tags_by_name(self.infoText.get_end_iter(), ' %s\n' % place.name, 'link') def goto_place(self, place): place = self.atlas[self.place] blink = (place.areaType == place.CIRCLE) if blink: coord = place.area[:2] else: coord = self.areas.calc_polygon_center(place.area) blink = blink and (place.type == place.CITY or place.type == place.PLACE) self.goto_coord(coord, blink) def goto_coord(self, coord, blink=False): x, y = self.areas.adjust_coord(coord) width = self.mapVp.allocation.width height = self.mapVp.allocation.height a = x - int(width / 2) b = y - int(height / 2) max_horiz = self.map_width - width max_vert = self.map_height - height if a < 0: a = 0 elif a > max_horiz: a = max_horiz if b < 0: b = 0 elif b > max_vert: b = max_vert if self.timeout: gobject.source_remove(self.timeout) self.pointer = (x, y) self.pointer_counter = 6 self.mapVp.get_hadjustment().set_value(a) self.mapVp.get_vadjustment().set_value(b) self.show_site() if blink: self.timeout = gobject.timeout_add(self.pointer_timeout, self.draw_pointer) def searchVisible(self, model, iter, user_data): search = self.searchEntry.get_text() if not search.strip(): return True return search.upper() in model.get_value(iter, 2).upper() def draw_pointer(self): if self.pointer_counter > 0: self.timeout = gobject.timeout_add(self.pointer_timeout, self.draw_pointer) self.draw_map() self.pointer_counter -= 1 def draw_map(self): self.map_pixmap.draw_pixbuf(None, self.map_pixbuf, 0, 0, 0, 0, self.map_pixbuf.props.width, self.map_pixbuf.props.height, gtk.gdk.RGB_DITHER_NORMAL, 0, 0) #debug if self.debug: self.debug_draw_bonds() #debug if not self.pointer_counter % 2: raio = int(self.pointer_raio * self.zoom) self.map_pixmap.draw_arc(self.gc, False, self.pointer[0] - raio, self.pointer[1] - raio, raio * 2, raio * 2, 0, 64 * 360) if len(self.marks) > 1: marks = [self.areas.adjust_coord(mark[1]) for mark in self.marks] self.map_pixmap.draw_lines(self.gc, marks) self.status.push(self.id, 'Distância: %.2f km' % self.areas.calc_distance(self.marks)) width = self.banner.props.width height = self.banner.props.height for coord in self.marks: coord_real = self.areas.adjust_coord(coord[1]) x = coord_real[0] - 1 y = coord_real[1] - height self.map_pixmap.draw_pixbuf(None, self.banner, 0, 0, x, y, width, height, gtk.gdk.RGB_DITHER_NORMAL, 0, 0) self.mapImg.set_from_pixmap(self.map_pixmap, None) def resize_map(self, zoom_factor): half_width = int(self.mapVp.allocation.width / 2) half_height = int(self.mapVp.allocation.height / 2) x = self.mapVp.get_hadjustment().get_value() + half_width y = self.mapVp.get_vadjustment().get_value() + half_height if self.zoom == 1.0: self.map_pixbuf = self.map_pixbuf_orig else: width = int(self.map_pixbuf_orig.get_width() * self.zoom) height = int(self.map_pixbuf_orig.get_height() * self.zoom) self.map_pixbuf = self.map_pixbuf_orig.scale_simple(width, height, gtk.gdk.INTERP_BILINEAR) self.map_width = self.map_pixbuf.props.width self.map_height = self.map_pixbuf.props.height self.map_pixmap = gtk.gdk.Pixmap(None, self.map_width, self.map_pixbuf.props.height, gtk.gdk.visual_get_system().depth) self.draw_map() gc.collect() x = int(x * zoom_factor) - half_width y = int(y * zoom_factor) - half_height self.mapVp.get_hadjustment().set_value(x) self.mapVp.get_vadjustment().set_value(y) def humanize_marks(self, marks): human_marks = [] for i in xrange(len(self.marks)): if self.marks[i][1][0] < 0: horz = 'Oeste' else: horz = 'Leste' if self.marks[i][1][1] < 0: vert = 'Sul' else: vert = 'Norte' distance = self.areas.calc_distance(self.marks[:i+1]) if self.marks[i][0]: place = self.marks[i][0] else: place = '' human_marks.append( (distance, place, '%dkm %s, %dkm %s' % (abs(self.marks[i][1][1]), vert, abs(self.marks[i][1][0]), horz) ) ) return human_marks def save_marks_to_file(self): if self.marks and self.marks_filename: xmldriver.save_marks(self.marks, self.marks_filename) def debug_draw_bonds(self): for polygon in self.areas.get_polygons(): self.map_pixmap.draw_lines(self.gc, polygon[1]) for circle in self.areas.get_circles(): circ = circle[1] self.map_pixmap.draw_arc(self.gc, False, circ[0] - circ[2], circ[1] - circ[2], circ[2] * 2, circ[2] * 2, 0, 64 * 360) if __name__ == '__main__': TagMap() gtk.main()