# GUIDirectory.py # $Id: GUIDirectory.py,v 1.27 2001/07/11 22:43:09 s2mdalle Exp $ # Written by David Allen # # Graphical representation of directory views. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. ######################################################################## from gopher import * from Tkinter import * import Pmw from string import * import GopherResource import GopherResponse import GopherConnection import ContentFrame import Cache import Options import ResourceInformation class GUIDirectory(ContentFrame.ContentFrame, Frame): # Behavior controllers verbose = None # Colors for various links. DEFAULT_COLOR = BLACK FOUND_COLOR = RED LINK_COLOR = BLUE ACTIVE_COLOR = RED # Constants for use with packing things in their correct locations. TYPE_COLUMN = 0 TYPE_SPAN = 1 NAME_COLUMN = 1 NAME_SPAN = 3 BLURB_COLUMN = 1 BLURB_SPAN = 3 CACHE_COLUMN = 4 CACHE_SPAN = 1 INFO_COLUMN = 5 INFO_SPAN = 2 HOSTPORT_COLUMN = 7 HOSTPORT_SPAN = 1 def __init__(self, parent_widget, parent_object, resp, resource, filename=None, menuAssocs={}): Frame.__init__(self, parent_widget) # Superclass constructor self.searchlist = [] # Searchable terms... self.parent = parent_object self.cursel = None self.resp = resp self.resource = resource self.menuAssocs = menuAssocs self.createPopup() pmsgbar = self.parent.getMessageBar() if pmsgbar: self.balloons = Pmw.Balloon(parent_widget, statuscommand=pmsgbar.helpmessage) else: self.balloons = Pmw.Balloon(parent_widget) if self.useStatusLabels: labeltext = "%s:%d" % (resource.getHost(), int(resource.getPort())) if resource.getName() != '' and resource.getLocator() != '': label2 = "\"%s\" ID %s" % (resource.getName(), resource.getLocator()) else: label2 = " " if len(label2) > 50: label2 = label2[0:47] + "..." Label(self, text=labeltext).pack(side='top', expand=0, fill='x') Label(self, text=label2).pack(side='top', expand=0, fill='x') self.scrolled_window = Pmw.ScrolledCanvas(self, borderframe=1, usehullsize=1, hull_width=400, hull_height=400, hscrollmode='dynamic', vscrollmode='dynamic') self.sbox = Frame(self.scrolled_window.interior()) self.scrolled_window.create_window(0, 0, anchor='nw', window=self.sbox) self.sbox.bind('', self.framePopupMenu) self.scrolled_window.interior().bind('', self.framePopupMenu) # As very LAST thing, pack it in. self.scrolled_window.pack(fill='both', expand=1) return None # End __init__ def pack_content(self, *args): responses = self.resp.getResponses() def color_widget(event, self=self, *args): wid = event.widget wid.configure(foreground=self.ACTIVE_COLOR) return None # Rather than having to fetch a specified option each time through # the loop, fetch them all at the beginning, since they shouldn't # change while we're packing content into the window. tmpopts = Options.program_options info_in_directories = tmpopts.getOption("display_info_in_directories") show_host_port = tmpopts.getOption("show_host_port") show_cached = tmpopts.getOption('show_cached') for x in range(0, len(responses)): if x % 50 == 1: self.scrolled_window.resizescrollregion() r = responses[x] rname = r.getName() rtype = r.getType() l = Label(self.sbox, text=rtype) if r.getTypeCode() == RESPONSE_BLURB: # Some servers prefix blurbs that are not meant to be displayed # with (NULL). Ferret these out, and just display a blank # line. if find(rname, "(NULL)") == 0: rname = " " blurb_label = Label(self.sbox, foreground=self.DEFAULT_COLOR, text=rname) blurb_label.grid(row=x, columnspan=self.BLURB_SPAN, col=self.BLURB_COLUMN, sticky=W) self.searchlist.append([rname, blurb_label, self.DEFAULT_COLOR]) else: # Trick Tk into passing arguments with the function and # get around Python's weird namespacing. def fn(event, self=self.parent, r=r, w=self, *args): self.goElsewhere(r) return None def dopopup(event, resource=r, self=self): # This binds resource to a parameter that # one of several popup commands will use. return self.popupMenu(event, resource) def enter_signal(event, resource=r, box=self.sbox, rowno=x, p=self): host = resource.getHost() port = resource.getPort() resource.__blurb__ = Label(box, text=resource.toURL()) resource.__blurb__.grid(row=rowno, col=p.HOSTPORT_COLUMN, columnspan=p.HOSTPORT_SPAN, sticky=E) def leave_signal(event, resource=r): try: resource.__blurb__.destroy() except: pass # Don't make it clickable if it's an error. But if it # isn't an error, connect these signals. if r.getTypeCode() != RESPONSE_ERR: default_color = self.LINK_COLOR b = Label(self.sbox, foreground=self.LINK_COLOR, text=r.getName()) b.bind('', fn) b.bind('', color_widget) b.bind('', dopopup) if show_host_port: # Cached option b.bind('', enter_signal) b.bind('', leave_signal) # Attach a balloon btext = r.toURL() self.balloons.bind(b, None, btext) else: default_color = self.DEFAULT_COLOR b = Label(self.sbox, foreground=self.DEFAULT_COLOR, text=rname) # Each entry in the searchlist is the name of a widget as a # string, the widget itself, and the widget's default color # The color is needed because 'finding' things requires # changing their color. self.searchlist.append([rname, b, default_color]) l.grid(row=x, col=self.TYPE_COLUMN, columnspan=self.TYPE_SPAN, sticky=W) b.grid(row=x, col=self.NAME_COLUMN, columnspan=self.NAME_SPAN, sticky=W) cacheobj = self.parent.getCache() if info_in_directories: # Cached option if r.getInfo() != None: i = r.getInfo() t = i.getAdmin() if len(t) > 40: t = t[0:40] Label(self.sbox, text=t).grid(row=x, col=self.INFO_COLUMN, columnspan=self.INFO_SPAN) else: Label(self.sbox, text=t).grid(row=x, col=self.INFO_COLUMN, columnspan=self.INFO_SPAN) else: Label(self.sbox, text=" ").grid(row=x, col=self.INFO_COLUMN, columnspan=self.INFO_SPAN) # Possibly report to the user whether or not a given file is # present in cache. I like to know this, but some people # don't, so make it a settable option. if show_cached: # Cached option if cacheobj.isInCache(r): Label(self.sbox, text="Cached").grid(row=x, col=self.CACHE_COLUMN, columnspan=self.CACHE_SPAN) else: Label(self.sbox, text=" ").grid(row=x, col=self.CACHE_COLUMN, columnspan=self.CACHE_SPAN) self.scrolled_window.resizescrollregion() return None def destroy(self, *args): self.pack_forget() self.scrolled_window.destroy() Frame.destroy(self) return None def viewSource(self, *args): wintitle = "Source: %s" % self.resource.toURL() dialog = Pmw.Dialog(self.parent, title=wintitle, buttons=()) textwid = Pmw.ScrolledText(dialog.interior(), hscrollmode='dynamic', vscrollmode='static') textwid.insert('end', self.resp.toProtocolString()) textwid.pack(expand=1, fill='both', side='top') return None def createPopup(self): """Pop-up menu on right click on a message""" self.popup = Menu(self) self.popup['tearoff'] = FALSE self.popup.add_command(label='Info', command=self.infoPopup) self.popup.add_command(label='Cache Status', command=self.cacheStatus) kz = self.menuAssocs.keys() kz.sort() for key in kz: self.popup.add_command(label=key, command=self.menuAssocs[key]) self.framePopup = Menu(self) self.framePopup['tearoff'] = FALSE self.framePopup.add_command(label='View Directory Source', command=self.viewSource) for key in self.menuAssocs.keys(): self.framePopup.add_command(label=key, command=self.menuAssocs[key]) def popupMenu(self, event, item): """Display pop-up menu on right click on a message""" self.cursel = item self.popup.tk_popup(event.x_root, event.y_root) return None def framePopupMenu(self, event): self.framePopup.tk_popup(event.x_root, event.y_root) return None def cacheStatus(self, resource=None): c = self.parent.getCache() if resource is None: resource = self.cursel resp = c.isInCache(resource) if resp is None: str = "Resource\n%s\nis not in the cache." % resource.toURL() self.parent.genericMessage(str, "Cache Status") else: url = resource.toURL() [filename, size] = resp str = "%s\nis in the cache as\n%s\n and is %s bytes" % (url, filename, size) self.parent.genericMessage(str, "Cache Status") return None def infoPopup(self, resource=None): if resource is None: resource = self.cursel if not resource.isGopherPlusResource(): # Info is only a valid operation if it's a Gopher+ resource. # Regular gopher servers don't provide any info in this way. str = "%s%s" % ("This item is not a Gopher+ Resource.\n", "No special information is available about it.") self.parent.genericError(str, "Error:") return None if resource.getInfo() is None: try: conn = GopherConnection.GopherConnection() info = conn.getInfo(resource) resource.setInfo(info) except Exception, errstr: url = resource.toURL() str = "Cannot display information about\n%s:\n%s" % (url, errstr) self.parent.genericError(str, "Error:") return None info = resource.getInfo() str = "Resource information:\n%s\n%s" % (resource.toURL(), info.toString()) gri = ResourceInformation.GUIResourceInformation(info) return None def find(self, term, caseSensitive=None, lastIdentifier=None): """Overrides the same definition in ContentFrame.ContentFrame""" if lastIdentifier is None: # Note this is a distinct case from when self.lastIndex = -1 # lastIdentifier is false (possibly 0) else: # This could be 0 or any positive number. self.lastIndex = lastIdentifier try: self.lastIndex = int(self.lastIndex) # Unstringify except: raise(Exception, "Illegal last ID passed to %s.find()" % self.__class__) if self.verbose: print "LAST FOUND INDEX was %s." % self.lastIndex # Bounds checking on lastIndex... if self.lastIndex < -1 or self.lastIndex > len(self.searchlist): print "*****Something's messed up. Bad lastIdentifier." elif self.lastIndex >= 0: [old_label, old_widget, color] = self.searchlist[self.lastIndex] old_widget.configure(foreground=color) found = None foundIndex = -1 for x in range(self.lastIndex+1, len(self.searchlist)): [label, label_widget, color] = self.searchlist[x] if self.verbose: print "Looking at index %d through \"%s\"..." % (x, label) if not caseSensitive: # If we're not doing case-sensitive compares, convert them # both to lower case and we'll compare them that way. label = lower(label) term = lower(term) if find(label, term) != -1: # Term was found in this label foundIndex = x found = 1 # Find only one match per call to this function. Bail out. break if found: [found_label, found_widget, color] = self.searchlist[foundIndex] # A bit of explanation on what this is: # to scroll to the correct location in the window (so the user # can see what was found and highlighted) we need a certain # distance to scroll the bar. Since directory entries are all # that's in the window, and there's one per line, this should # work. scrlratio = float(foundIndex) / float(len(self.searchlist)) if self.verbose: print "Scrlratio is: \"%s\"" % scrlratio print "Found \"%s\" in \"%s\" at index %d" % (term, found_label, foundIndex) # Turn the one that was found into FOUND_COLOR found_widget.configure(foreground=self.FOUND_COLOR) # Scroll the window to scrlratio - this should place the found # item at the very top of the window. self.scrolled_window.yview('moveto', scrlratio) if self.verbose: print "RETURNING NEW FOUND INDEX: %s" % foundIndex return foundIndex else: return None # This signals that it wasn't found, and to reset # Everything else this class needs gets inherited from other classes.