class GraphDisplay(Display): if "droidsansmono" in pygame.font.get_fonts(): STATUSBARFONT = pygame.font.match_font("droidsansmono") else: STATUSBARFONT = pygame.font.get_default_font() ANIM_STEP = 0.03 KEY_REPEAT = (500, 30) STATUSBAR_ALPHA = 0.75 STATUSBAR_FGCOLOR = (255, 255, 80) STATUSBAR_BGCOLOR = (128, 0, 0) STATUSBAR_OVERFLOWCOLOR = (255, 0, 0) HELP_ALPHA = 0.95 HELP_FGCOLOR = (255, 255, 80) HELP_BGCOLOR = (0, 128, 0) INPUT_ALPHA = 0.75 INPUT_FGCOLOR = (255, 255, 80) INPUT_BGCOLOR = (0, 0, 128) KEYS = { 'meta -': ('zoom', 0.5), '-': ('zoom', 0.5), 'meta plus': ('zoom', 2.0), 'plus': ('zoom', 2.0), 'meta 0': 'zoom_actual_size', '0': 'zoom_actual_size', 'meta 1': 'zoom_to_fit', '1': 'zoom_to_fit', 'meta f4': 'quit', 'meta quit': 'quit', 'quit': 'quit', 'meta right': 'layout_forward', 'meta left': 'layout_back', 'backspace': 'layout_back', 'f': 'search', '/': 'search', 'n': 'find_next', 'p': 'find_prev', 'r': 'reload', 'left': ('pan', (-1, 0)), 'right': ('pan', (1, 0)), 'up': ('pan', (0, -1)), 'down': ('pan', (0, 1)), 'shift left': ('fast_pan', (-1, 0)), 'shift right': ('fast_pan', (1, 0)), 'shift up': ('fast_pan', (0, -1)), 'shift down': ('fast_pan', (0, 1)), 'help': 'help', 'space': 'hit', "w": "toggle_whitespace", "a": "toggle_ast", } HELP_MSG = """ Key bindings: +, = or . Zoom in - Zoom out 1 Zoom to fit 0 Actual size Arrows Scroll Shift+Arrows Scroll faster Space Follow word link Backspace Go back in history Meta Left Go back in history Meta Right Go forward in history R Reload the page W Toggle whitespace nodes A Toggle AST/Parsetree F or / Search for text N Find next occurrence P Find previous occurrence F1, H or ? This help message Q or Esc Quit Mouse bindings: Click on objects to move around Drag with the left mouse button to scroll Drag with the right mouse button to zoom in/out Use scroll wheel do scroll up or down """.replace('\n ', '\n').strip() # poor man's dedent def __init__(self, layout): super(GraphDisplay, self).__init__() self.font = pygame.font.Font(self.STATUSBARFONT, 16) self.viewers_history = [] self.forward_viewers_history = [] self.highlight_word = None self.highlight_obj = None self.viewer = None self.method_cache = {} self.key_cache = {} self.ascii_key_cache = {} self.status_bar_height = 0 self.searchstr = None self.searchpos = 0 self.searchresults = [] self.initialize_keys() self.setlayout(layout) def initialize_keys(self): pygame.key.set_repeat(*self.KEY_REPEAT) mask = 0 for strnames, methodname in self.KEYS.iteritems(): names = strnames.split() if not isinstance(methodname, basestring): methodname, args = methodname[0], methodname[1:] else: args = () method = getattr(self, methodname, None) if method is None: print 'Can not implement key mapping %r, %s.%s does not exist' % ( strnames, self.__class__.__name__, methodname) continue mods = [] basemod = 0 keys = [] for name in names: if name in METAKEYS: val = METAKEYS[name] if not isinstance(val, int): mods.append(tuple([METAKEYS[k] for k in val])) else: basemod |= val else: val = GET_KEY(name) assert len(keys) == 0 if not isinstance(val, (int, basestring)): keys.extend([GET_KEY(k) for k in val]) else: keys.append(val) assert keys for key in keys: if isinstance(key, int): for mod in permute_mods(basemod, mods): self.key_cache[(key, mod)] = (method, args) mask |= mod else: for mod in permute_mods(basemod, mods): char = key.lower() mod = mod & ~KMOD_SHIFT self.ascii_key_cache[(char, mod)] = (method, args) mask |= mod self.key_mask = mask def help(self): """Show a help window and wait for a key or a mouse press.""" margin_x = margin_y = 64 padding_x = padding_y = 8 fgcolor = self.HELP_FGCOLOR bgcolor = self.HELP_BGCOLOR helpmsg = self.HELP_MSG width = self.width - 2 * margin_x height = self.height - 2 * margin_y lines = rendertext(helpmsg, self.font, fgcolor, width - 2 * padding_x, height - 2 * padding_y) block = pygame.Surface((width, height), SWSURFACE | SRCALPHA) block.fill(bgcolor) sx = padding_x sy = padding_y for img in lines: w, h = img.get_size() block.blit(img, (sx, sy)) sy += h block.set_alpha(int(255 * self.HELP_ALPHA)) self.screen.blit(block, (margin_x, margin_y)) pygame.display.flip() while True: wait_for_events() e = EventQueue.pop(0) if e.type in (MOUSEBUTTONDOWN, KEYDOWN, QUIT): break if e.type == QUIT: EventQueue.insert(0, e) # re-insert a QUIT self.must_redraw = True def input(self, prompt): """Ask the user to input something. Returns the string that the user entered, or None if the user pressed Esc. """ def draw(text): margin_x = margin_y = 0 padding_x = padding_y = 8 fgcolor = self.INPUT_FGCOLOR bgcolor = self.INPUT_BGCOLOR width = self.width - 2 * margin_x lines = renderline(text, self.font, fgcolor, width - 2 * padding_x) height = totalheight(lines) + 2 * padding_y block = pygame.Surface((width, height), SWSURFACE | SRCALPHA) block.fill(bgcolor) sx = padding_x sy = padding_y for img in lines: w, h = img.get_size() block.blit(img, (sx, sy)) sy += h block.set_alpha(int(255 * self.INPUT_ALPHA)) # This can be slow. It would be better to take a screenshot # and use it as the background. self.viewer.render() if self.statusbarinfo: self.drawstatusbar() self.screen.blit(block, (margin_x, margin_y)) pygame.display.flip() draw(prompt) text = "" self.must_redraw = True while True: wait_for_events() old_text = text events = EventQueue[:] del EventQueue[:] for e in events: if e.type == QUIT: EventQueue.insert(0, e) # re-insert a QUIT return None elif e.type == KEYDOWN: if e.key == K_ESCAPE: return None elif e.key == K_RETURN: return forcestr(text) # return encoded unicode elif e.key == K_BACKSPACE: text = text[:-1] elif e.unicode and ord(e.unicode) >= ord(' '): text += e.unicode if text != old_text: draw(prompt + text) def hit(self): word = self.highlight_word if word is not None: if word in self.layout.links: self.setstatusbar('loading...') self.redraw_now() self.layout.request_followlink(word) def search(self): searchstr = self.input('Find: ') if not searchstr: return self.searchstr = searchstr self.searchpos = -1 self.searchresults = list(self.viewer.findall(self.searchstr)) self.find_next() def find_next(self): if not self.searchstr: return if self.searchpos + 1 >= len(self.searchresults): self.setstatusbar('Not found: %s' % self.searchstr) return self.searchpos += 1 self.highlight_found_item() def find_prev(self): if not self.searchstr: return if self.searchpos - 1 < 0: self.setstatusbar('Not found: %s' % self.searchstr) return self.searchpos -= 1 self.highlight_found_item() def highlight_found_item(self): item = self.searchresults[self.searchpos] self.sethighlight(obj=item) msg = 'Found %%s containing %s (%d/%d)' % (self.searchstr.replace( '%', '%%'), self.searchpos + 1, len(self.searchresults)) if isinstance(item, Node): self.setstatusbar(msg % 'node') self.look_at_node(item, keep_highlight=True) elif isinstance(item, Edge): self.setstatusbar(msg % 'edge') self.look_at_edge(item, keep_highlight=True) else: # should never happen self.setstatusbar(msg % item) def setlayout(self, layout): if self.viewer and getattr(self.viewer.graphlayout, 'key', True) is not None: self.viewers_history.append(self.viewer) del self.forward_viewers_history[:] self.layout = layout self.viewer = GraphRenderer(self.screen, layout) self.searchpos = 0 self.searchresults = [] self.zoom_to_fit() def zoom_actual_size(self): self.viewer.shiftscale(float(self.viewer.SCALEMAX) / self.viewer.scale) self.updated_viewer() def calculate_zoom_to_fit(self): return min( float(self.width) / self.viewer.width, float(self.height) / self.viewer.height, float(self.viewer.SCALEMAX) / self.viewer.scale) def zoom_to_fit(self): """ center and scale to view the whole graph """ f = self.calculate_zoom_to_fit() self.viewer.shiftscale(f) self.updated_viewer() def zoom(self, scale): self.viewer.shiftscale(max(scale, self.calculate_zoom_to_fit())) self.updated_viewer() def reoffset(self): self.viewer.reoffset(self.width, self.height) def pan(self, (x, y)): self.viewer.shiftoffset(x * (self.width // 8), y * (self.height // 8)) self.updated_viewer()
class GraphDisplay(Display): STATUSBARFONT = FIXEDFONT ANIM_STEP = 0.03 KEY_REPEAT = (500, 30) STATUSBAR_ALPHA = 0.75 STATUSBAR_FGCOLOR = (255, 255, 80) STATUSBAR_BGCOLOR = (128, 0, 0) STATUSBAR_OVERFLOWCOLOR = (255, 0, 0) HELP_ALPHA = 0.95 HELP_FGCOLOR = (255, 255, 80) HELP_BGCOLOR = (0, 128, 0) INPUT_ALPHA = 0.75 INPUT_FGCOLOR = (255, 255, 80) INPUT_BGCOLOR = (0, 0, 128) KEYS = { 'meta -' : ('zoom', 0.5), '-' : ('zoom', 0.5), 'meta plus' : ('zoom', 2.0), 'plus' : ('zoom', 2.0), 'meta 0' : 'zoom_actual_size', '0' : 'zoom_actual_size', 'meta 1' : 'zoom_to_fit', '1' : 'zoom_to_fit', 'meta f4' : 'quit', 'meta quit' : 'quit', 'quit' : 'quit', 'meta right' : 'layout_forward', 'meta left': 'layout_back', 'backspace' : 'layout_back', 'f': 'search', '/': 'search', 'n': 'find_next', 'p': 'find_prev', 'r': 'reload', 'left' : ('pan', (-1, 0)), 'right' : ('pan', (1, 0)), 'up' : ('pan', (0, -1)), 'down' : ('pan', (0, 1)), 'shift left' : ('fast_pan', (-1, 0)), 'shift right' : ('fast_pan', (1, 0)), 'shift up' : ('fast_pan', (0, -1)), 'shift down' : ('fast_pan', (0, 1)), 'help': 'help', 'space': 'hit', } HELP_MSG = """ Key bindings: +, = or . Zoom in - Zoom out 1 Zoom to fit 0 Actual size Arrows Scroll Shift+Arrows Scroll faster Space Follow word link Backspace Go back in history Meta Left Go back in history Meta Right Go forward in history R Reload the page F or / Search for text N Find next occurrence P Find previous occurrence F1, H or ? This help message Q or Esc Quit Mouse bindings: Click on objects to move around Drag with the left mouse button to zoom in/out Drag with the right mouse button to scroll """.replace('\n ', '\n').strip() # poor man's dedent def __init__(self, layout): super(GraphDisplay, self).__init__() self.font = pygame.font.Font(self.STATUSBARFONT, 16) self.viewers_history = [] self.forward_viewers_history = [] self.highlight_word = None self.highlight_obj = None self.viewer = None self.method_cache = {} self.key_cache = {} self.ascii_key_cache = {} self.status_bar_height = 0 self.searchstr = None self.searchpos = 0 self.searchresults = [] self.initialize_keys() self.setlayout(layout) def initialize_keys(self): pygame.key.set_repeat(*self.KEY_REPEAT) mask = 0 for strnames, methodname in self.KEYS.iteritems(): names = strnames.split() if not isinstance(methodname, basestring): methodname, args = methodname[0], methodname[1:] else: args = () method = getattr(self, methodname, None) if method is None: print 'Can not implement key mapping %r, %s.%s does not exist' % ( strnames, self.__class__.__name__, methodname) continue mods = [] basemod = 0 keys = [] for name in names: if name in METAKEYS: val = METAKEYS[name] if not isinstance(val, int): mods.append(tuple([METAKEYS[k] for k in val])) else: basemod |= val else: val = GET_KEY(name) assert len(keys) == 0 if not isinstance(val, (int, basestring)): keys.extend([GET_KEY(k) for k in val]) else: keys.append(val) assert keys for key in keys: if isinstance(key, int): for mod in permute_mods(basemod, mods): self.key_cache[(key, mod)] = (method, args) mask |= mod else: for mod in permute_mods(basemod, mods): char = key.lower() mod = mod & ~KMOD_SHIFT self.ascii_key_cache[(char, mod)] = (method, args) mask |= mod self.key_mask = mask def help(self): """Show a help window and wait for a key or a mouse press.""" margin_x = margin_y = 64 padding_x = padding_y = 8 fgcolor = self.HELP_FGCOLOR bgcolor = self.HELP_BGCOLOR helpmsg = self.HELP_MSG width = self.width - 2*margin_x height = self.height - 2*margin_y lines = rendertext(helpmsg, self.font, fgcolor, width - 2*padding_x, height - 2*padding_y) block = pygame.Surface((width, height), SWSURFACE | SRCALPHA) block.fill(bgcolor) sx = padding_x sy = padding_y for img in lines: w, h = img.get_size() block.blit(img, (sx, sy)) sy += h block.set_alpha(int(255 * self.HELP_ALPHA)) self.screen.blit(block, (margin_x, margin_y)) pygame.display.flip() while True: wait_for_events() e = EventQueue.pop(0) if e.type in (MOUSEBUTTONDOWN, KEYDOWN, QUIT): break if e.type == QUIT: EventQueue.insert(0, e) # re-insert a QUIT self.must_redraw = True def input(self, prompt): """Ask the user to input something. Returns the string that the user entered, or None if the user pressed Esc. """ def draw(text): margin_x = margin_y = 0 padding_x = padding_y = 8 fgcolor = self.INPUT_FGCOLOR bgcolor = self.INPUT_BGCOLOR width = self.width - 2*margin_x lines = renderline(text, self.font, fgcolor, width - 2*padding_x) height = totalheight(lines) + 2 * padding_y block = pygame.Surface((width, height), SWSURFACE | SRCALPHA) block.fill(bgcolor) sx = padding_x sy = padding_y for img in lines: w, h = img.get_size() block.blit(img, (sx, sy)) sy += h block.set_alpha(int(255 * self.INPUT_ALPHA)) # This can be slow. It would be better to take a screenshot # and use it as the background. self.viewer.render() if self.statusbarinfo: self.drawstatusbar() self.screen.blit(block, (margin_x, margin_y)) pygame.display.flip() draw(prompt) text = "" self.must_redraw = True while True: wait_for_events() old_text = text events = EventQueue[:] del EventQueue[:] for e in events: if e.type == QUIT: EventQueue.insert(0, e) # re-insert a QUIT return None elif e.type == KEYDOWN: if e.key == K_ESCAPE: return None elif e.key == K_RETURN: return text.encode('latin-1') # XXX do better elif e.key == K_BACKSPACE: text = text[:-1] elif e.unicode and ord(e.unicode) >= ord(' '): text += e.unicode if text != old_text: draw(prompt + text) def hit(self): word = self.highlight_word if word is not None: if word in self.layout.links: self.setstatusbar('loading...') self.redraw_now() self.layout.request_followlink(word) def search(self): searchstr = self.input('Find: ') if not searchstr: return self.searchstr = searchstr self.searchpos = -1 self.searchresults = list(self.viewer.findall(self.searchstr)) self.find_next() def find_next(self): if not self.searchstr: return if self.searchpos + 1 >= len(self.searchresults): self.setstatusbar('Not found: %s' % self.searchstr) return self.searchpos += 1 self.highlight_found_item() def find_prev(self): if not self.searchstr: return if self.searchpos - 1 < 0: self.setstatusbar('Not found: %s' % self.searchstr) return self.searchpos -= 1 self.highlight_found_item() def highlight_found_item(self): item = self.searchresults[self.searchpos] self.sethighlight(obj=item) msg = 'Found %%s containing %s (%d/%d)' % ( self.searchstr.replace('%', '%%'), self.searchpos+1, len(self.searchresults)) if isinstance(item, Node): self.setstatusbar(msg % 'node') self.look_at_node(item, keep_highlight=True) elif isinstance(item, Edge): self.setstatusbar(msg % 'edge') self.look_at_edge(item, keep_highlight=True) else: # should never happen self.setstatusbar(msg % item) def setlayout(self, layout): if self.viewer and getattr(self.viewer.graphlayout, 'key', True) is not None: self.viewers_history.append(self.viewer) del self.forward_viewers_history[:] self.layout = layout self.viewer = GraphRenderer(self.screen, layout) self.searchpos = 0 self.searchresults = [] self.zoom_to_fit() def zoom_actual_size(self): self.viewer.shiftscale(float(self.viewer.SCALEMAX) / self.viewer.scale) self.updated_viewer() def calculate_zoom_to_fit(self): return min(float(self.width) / self.viewer.width, float(self.height) / self.viewer.height, float(self.viewer.SCALEMAX) / self.viewer.scale) def zoom_to_fit(self): """ center and scale to view the whole graph """ f = self.calculate_zoom_to_fit() self.viewer.shiftscale(f) self.updated_viewer() def zoom(self, scale): self.viewer.shiftscale(max(scale, self.calculate_zoom_to_fit())) self.updated_viewer() def reoffset(self): self.viewer.reoffset(self.width, self.height) def pan(self, (x, y)): self.viewer.shiftoffset(x * (self.width // 8), y * (self.height // 8)) self.updated_viewer()