forg-0.5.1.orig/0040755000175000017500000000000007455600532013653 5ustar jgoerzenjgoerzenforg-0.5.1.orig/older/0040755000175000017500000000000007344462645014770 5ustar jgoerzenjgoerzenforg-0.5.1.orig/older/DirCanvas.py0100644000175000017500000000266607344462645017223 0ustar jgoerzenjgoerzen# DirCanvas.py # Written by David Allen # Takes a GopherResource and a GopherResponse object and turns it into # a canvas object that displays the contents of the directory on screen. # # This is acting a bit strange to say the least, so for now, we stick with the # other method. ########################################################################### import gopher from GopherResponse import * from Tkinter import * import Pmw class DirCanvas: DirCanvasException = "You can't do that to me, that's filthy!" def __init__(self, parent, resource, response): self.canvas = Pmw.ScrolledCanvas(parent, vscrollmode='dynamic', hscrollmode='dynamic') self.pack = self.canvas.pack self.resource = resource self.response = response self.baselinex = 50 self.baseliney = 50 resps = self.response.getResponses() for x in range(0, len(resps)): r = resps[x] basex = self.baselinex basey = self.baseliney * x if r.getTypeCode() == RESPONSE_BLURB: self.canvas.create_text(basex, basey, text=r.getName()) else: self.canvas.create_text(basex, basey, text=r.getType()) basex = basex + 100 self.canvas.create_text(basex, basey, text=r.getName()) return None forg-0.5.1.orig/older/GopherGUI.py0100644000175000017500000002474107344462645017140 0ustar jgoerzenjgoerzen#!/usr/bin/python # GopherGUI.py # Written by David Allen # Released under the terms of the GNU General Public License # # This module does most of the graphical interface work related to the program. # In order to start the main program, import gtk, create a GopherGUI object, # and call mainloop() in gtk. ############################################################################### # Python-wide modules. import os import string import regsub # GUI specific modules. from gtk import * from GDK import * from TextFrame import * # Data structures, connections, and the like. from GopherConnection import * from GopherResponse import * from gopher import * from List import * from Associations import * GLOBAL_WINDOW_COUNT = 0 class GopherGUI: def __init__(self): WIDTH = 600 HEIGHT = 600 self.WIDGET_ENTRY = 0 self.RESOURCE_ENTRY = 1 self.RESPONSE_ENTRY = 2 self.currentIndex = -1 # self.navList contains ONLY ListNode() objects self.navList = List() self.currentContent = None self.createAssociations() self.window = GtkWindow(WINDOW_TOPLEVEL) self.window.set_default_size(width=WIDTH, height=HEIGHT) self.frame = GtkVBox(0, 0) self.topBar = GtkHBox(0, 0) self.hostEntry = GtkEntry() self.portEntry = GtkEntry() self.locatorEntry = GtkEntry() self.portEntry.set_max_length(6) self.okHostPort = GtkButton(label=" Go ") self.okHostPort.connect("clicked", self.go) self.topBar.pack_start(GtkLabel("Host: "), expand=FALSE, fill=FALSE) self.topBar.pack_start(self.hostEntry, expand=TRUE, fill=TRUE) self.topBar.pack_start(GtkLabel("Locator: "), expand=FALSE, fill=FALSE) self.topBar.pack_start(self.locatorEntry, expand=FALSE, fill=TRUE) self.topBar.pack_start(GtkLabel("Port: "), expand=FALSE, fill=FALSE) self.topBar.pack_start(self.portEntry, expand=FALSE, fill=FALSE) self.topBar.pack_start(self.okHostPort, expand=FALSE, fill=FALSE) self.hostEntry.set_text("quux.org") self.portEntry.set_text("70") self.menuBar = GtkMenuBar() menus = self.makeMenus() for menu in menus: self.menuBar.append(menu) self.handleBox = GtkHandleBox() self.handleBox.add(self.menuBar) self.notebook = GtkNotebook() self.notebook.set_homogeneous_tabs(FALSE) resource = GopherResource(RESPONSE_DIR, "/", "quux.org", 70, "Quux.org root") self.CONTENT_BOX = GtkVBox(0,0) contentKey = self.createContentFrame(resource) self.changeContent(contentKey[0]) # The data for ListNodes in this list is a three-item list: # [widget, resource, response] # widget is the actual GTK+ widget that is packed into the screen. # resource is the GopherResource object used to identify a connection. # response is the GopherRespones object used to cache the output of # a qeury with the server. self.navList.insert(ListNode(contentKey)) self.navBar = GtkHBox(0,0) forward = GtkButton("Forward") back = GtkButton("Back") forward.connect("clicked", self.goForward) back.connect("clicked", self.goBackward) self.navBar.pack_start(forward, expand=FALSE, fill=FALSE) self.navBar.pack_start(back, expand=FALSE, fill=FALSE) self.frame.pack_start(self.handleBox, expand=FALSE, fill=TRUE) self.frame.pack_start(self.topBar, expand=FALSE, fill=TRUE) self.frame.pack_start(self.navBar, expand=FALSE, fill=TRUE) self.frame.pack_start(self.CONTENT_BOX, expand=TRUE, fill=TRUE) self.window.signal_connect('destroy', self.destroy) self.window.add(self.frame) return None def changeContent(self, newwid): if self.currentContent != None: self.currentContent.destroy() self.CONTENT_BOX.pack_start(newwid, expand=TRUE, fill=TRUE) self.currentContent = newwid return self.currentContent def createAssociations(self, *args): self.associations = Associations() images = [".gif", ".jpg", ".bmp", ".xpm", ".xbm", ".png", ".jpeg", ".tif", ".tiff" ] for item in images: self.associations.addAssociation(item, "eeyes $1") browser_stuff = [".html", ".htm", ".css"] for item in browser_stuff: self.associations.addAssociation(item, "netscape $1") def printItems(self): print "Items on stack:" for item in self.contentStack: print item.toString() def xpmToWidget(self, file, parent): pixmap = None return pixmap def go(self, *rest): host = self.hostEntry.get_text() port = self.portEntry.get_text() locator = self.locatorEntry.get_text() if locator == '': locator = "/" res = GopherResource('1', locator, host, port, "%s Root" % host) self.goElsewhere(None, res) def goElsewhere(self, emitter, resource): contentKey = self.createContentFrame(resource) self.changeContent(contentKey[0]) self.navList.insert(ListNode(contentKey)) return None def goForward(self, *rest): try: node = self.navList.getNext() wid = self.responseToWidget(data[self.RESPONSE_ENTRY], data[self.RESOURCE_ENTRY]) self.changeContent(wid) except ListException, errstr: print "Couldn't get next: %s" % errstr return None def goBackward(self, *rest): try: node = self.navList.getPrev() data = node.getData() wid = self.responseToWidget(data[self.RESPONSE_ENTRY], data[self.RESOURCE_ENTRY]) self.changeContent(wid) except ListException, errstr: print "Couldn't get prev: %s" % errstr def clone(self, *args): x = GopherGUI() x.show() return None def createContentFrame(self, resource): conn = GopherConnection("quux.org", 70) resp = conn.getResource(resource) wid = self.responseToWidget(resp, resource) return [wid, resource, resp] # Takes a response object and turns it into a graphical box. def responseToWidget(self, resp, resource): labeltext = "%s:%d" % (resource.getHost(), int(resource.getPort())) label2 = "\"%s\" ID %s" % (resource.getName(), resource.getLocator()) scrolled_window = GtkScrolledWindow() sbox = GtkVBox(0, 0) sbox.pack_start(GtkLabel(labeltext), expand=FALSE, fill=FALSE) sbox.pack_start(GtkLabel(label2), expand=FALSE, fill=FALSE) if resp.getData() != None: pane = GtkVBox(0, 0) print "DATA:" print resp.getData() textwid = TextFrame(regsub.sub("\r", "", resp.getData())) pane.pack_start(textwid, expand=TRUE, fill=TRUE) pane.show_all() return pane else: print "RESPONSES:" pane = GtkVBox(0, 0) responses = resp.getResponses() table = GtkTable(rows=len(responses), cols=2, homogeneous=FALSE) for x in range(0, len(responses)): r = responses[x] print "Response: %s" % r.getName() b = GtkButton(r.getName()) b.signal_connect("clicked", self.goElsewhere, r) b.set_relief(RELIEF_NONE) table.attach(GtkLabel(r.getType()), 0, 1, x, (x+1), xoptions=0, yoptions=0, ypadding=0, xpadding=0) table.attach(b, 1, 2, x, (x+1), xoptions=EXPAND|FILL, yoptions=0, ypadding=0, xpadding=0) pane.pack_start(table, expand=TRUE, fill=TRUE) sbox.pack_start(pane, expand=TRUE, fill=TRUE) scrolled_window.add_with_viewport(sbox) scrolled_window.show_all() return scrolled_window return None # Never happens def closeWindow(self, *args): self.window.destroy() # GLOBAL_WINDOW_COUNT = GLOBAL_WINDOW_COUNT - 1 # if GLOBAL_WINDOW_COUNT == 0: # mainquit() def destroy(self, data=None, auxdata=None): self.window.destroy() mainquit() return None def show(self): self.window.set_policy(FALSE, TRUE, TRUE) self.window.show_all() return None def makeMenus(self): fileMenu = GtkMenuItem(label="File") helpMenu = GtkMenuItem(label="Help") navMenu = GtkMenuItem(label="Navigation") fm = GtkMenu() nm = GtkMenu() hm = GtkMenu() file_new_win = GtkMenuItem(label="New Window") file_new_win.connect("activate", self.clone) file_close_win = GtkMenuItem(label="Close Window") file_close_win.connect("activate", self.closeWindow) file_exit = GtkMenuItem(label='Exit') file_exit.connect('activate', self.destroy) nav_forward = GtkMenuItem(label="Forward") nav_backward = GtkMenuItem(label="Backward") nav_forward.connect("activate", self.goForward) nav_backward.connect("activate", self.goBackward) help_about = GtkMenuItem(label="About") # help_test.connect('activate', self.testCurrent) fm.append(file_new_win) fm.append(file_close_win) fm.append(file_exit) nm.append(nav_forward) nm.append(nav_backward) hm.append(help_about) fileMenu.set_submenu(fm) navMenu.set_submenu(nm) helpMenu.set_submenu(hm) return [fileMenu, navMenu, helpMenu] ######################### MAIN CODE ######################################### GLOBAL_WINDOW_COUNT = 0 x = GopherGUI() x.show() mainloop() forg-0.5.1.orig/older/README.txt0100644000175000017500000000071007344462645016461 0ustar jgoerzenjgoerzenIn this directory you will find ancient code that went with an extremely early version of this program written for the python GTK+ bindings. Since we wanted the program to run on more than just well-supported GTK+ platforms, we moved to Tkinter (Tk) and we no longer use these modules. It is extremely unlikely that you will find anything here of any use unless you're a developer who enjoys a good chuckle out of another developer's pathetic flailings. forg-0.5.1.orig/older/TextFrame.py0100644000175000017500000001303307344462645017236 0ustar jgoerzenjgoerzen#!env python # Written by David Allen # http://opop.nols.com/ # Released under the terms of the GNU General Public License ######################################################################## from gtk import * class TextFrame(GtkTable): def __init__(self, data): GtkTable.__init__(self, rows=3, cols=2, homogeneous=FALSE) self.popupMenu = self.makePopupMenu() self.data = data self.text = GtkText() self.text.set_editable(FALSE) self.text.set_word_wrap(FALSE) self.text.set_line_wrap(FALSE) self.attach(self.text, 0, 1, 0, 1, xoptions=EXPAND|FILL, yoptions=EXPAND|FILL, ypadding=0, xpadding=0) self.vscroll = GtkVScrollbar(self.text.get_vadjustment()) self.attach(self.vscroll, 1, 2, 0, 1, xoptions=FILL, yoptions=FILL, ypadding=0, xpadding=0) self.hscroll = GtkHScrollbar(self.text.get_hadjustment()) self.attach(self.hscroll, 0, 1, 1, 2, xoptions=FILL, yoptions=FILL, ypadding=0, xpadding=0) self.messageBox = GtkVBox(0, 0) self.attach(self.messageBox, 0, 2, 2, 3, xoptions=FILL, yoptions=FILL, ypadding=0, xpadding=0) self.connect('button_press_event', self.contextPopup) self.loadData() self.show_all() return None def saveDialog(self, *args): fs = GtkFileSelection() fs.ok_button.connect('clicked', self.saveToFile, fs) fs.cancel_button.connect('clicked', lambda *args: args[2].destroy()) fs.show_all() def makePopupMenu(self): pmenu = GtkMenu() save = GtkMenuItem(label="Save") close = GtkMenuItem(label="Close") save.connect('activate', self.saveDialog) close.connect('activate', self.destroy) pmenu.append(save) pmenu.append(close) return pmenu def saveToFile(self, emitter, fs, *stuff): filename = fs.get_filename() fs.destroy() try: fp = open(filename, "w") except: print "Error opening file" return None ln = self.text.get_length() pt = 0 while pt < ln: endpoint = pt + 1024 if endpoint > ln: endpoint = ln try: fp.write(self.text.get_chars(pt, endpoint)) except IOError, errstr: print "Error writing data to file: %s" % errstr return None pt = pt + 1024 fp.flush() fp.close() return 1 def contextPopup(self, emitter, event): if event.button == 3: self.popupMenu.popup(None, None, None, event.button, event.time) self.popupMenu.show_all() return None def loadData(self): self.insert(None, None, None, self.data) def test(self, *args): self.notify("This is a test") self.notify("This is another test") self.notify("This is yet another test") # Bunch of crap text methods. This is the downside of extending # GtkTable instead of GtkText which is the meat of the widget. :) def backward_delete(self, nchars): return self.text.backward_delete(nchars) def freeze(self, obj=None): return self.text.freeze(obj) def get_hadjustment(self): return self.text.get_hadjustment() def get_length(self): return self.text.get_length() def get_point(self): return self.text.get_point() def get_vadjustment(self): return self.text.get_vadjustment() def insert(font, fg, bg, string): return self.text.insert(font, fg, bg, string) def insert_defaults(self, chars): return self.text.insert_defaults(chars) def set_adjustments(self, hadj, vadj): return self.text.set_adjustments(hadj, vadj) def set_editable(self, editable): return self.text.set_editable(editable) def set_line_wrap(self, line_wrap): return self.text.set_line_wrap(line_wrap) def thaw(self, obj=None): return self.text.thaw(obj) def set_point(self, point): return self.text.set_point(point) def forward_delete(self, length): return self.text.forward_delete(length) def set_word_wrap(self, word_wrap): return self.text.set_word_wrap(word_wrap) def insert(self, o, t, th, text): return self.text.insert(o, t, th, text) def changed(self): return self.text.changed() def claim_selection(self, claim, time): return self.text.claim_selection(claim, time) def copy_clipboard(self): return self.text.copy_clipboard() def cut_clipboard(self): return self.text.cut_clipboard() def delete_selection(self): return self.text.delete_selection() def delete_text(self, start, end): return self.text.delete_text(self, start, end) def get_chars(self, start, end): return self.text.get_chars(start, end) def get_position(self): return self.text.get_position() def insert_text(self, new_text): return self.text.insert_text(new_text) def paste_clipboard(self): return self.text.paste_clipboard() def select_region(self, start, end): return self.text.select_region(start, end) def set_editable(self, is_editable): return self.text.set_editable(is_editable) def set_position(self, pos): return self.text.set_position(pos) forg-0.5.1.orig/older/treebackup.py0100644000175000017500000007033707344462645017476 0ustar jgoerzenjgoerzen# Tree.py # highly optimized tkinter tree control # Written by Charles E. "Gene" Cash # Modifications by David Allen # # 98/12/02 CEC started # 99/??/?? CEC release to comp.lang.python.announce # # to do: # add working "busy" cursor # make paste more general # add good ideas from win95's tree common control # # 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. ############################################################################ import os import string import Tkdnd from Tkinter import * # this is initialized later, after Tkinter is started open_icon=None #------------------------------------------------------------------------------ # cut'n'paste helper object class Cut_Object: def __init__(self, node=None, id=None, state=None): self.node=node self.full_id=id self.state=state #------------------------------------------------------------------------------ # tree node helper class class Node: # initialization creates node, draws it, and binds mouseclicks def __init__(self, parent, name, id, closed_icon, open_icon, x, y, parentwidget): # lots of things to remember # immediate parent node self.parent = parent # name displayed on the label self.name = name # internal name used to manipulate things self.id = id # bitmaps to be displayed self.open_icon = open_icon self.closed_icon = closed_icon # tree widget we belong to self.widget = parentwidget # our list of child nodes self.subnodes = [] # cheap mutex spinlock self.spinlock = 0 # closed to start with self.open_flag=0 # draw horizontal connecting lines if self.widget.lineflag: self.line = self.widget.create_line(x-self.widget.distx, y, x, y) # draw approprate image self.symbol = self.widget.create_image(x, y, image=self.closed_icon) # add label self.label = self.widget.create_text(x+self.widget.textoff, y, text=self.name, justify='left', anchor='w' ) # single-click to expand/collapse cmd = self.widget.tag_bind(self.symbol, '<1>', self.click) # drag'n'drop support if self.widget.dnd_hook: # this starts the drag operation self.widget.tag_bind(self.label, '<1>', lambda ev, qs=self:qs.widget.dnd_click(ev, qs)) # these events help identify drop target node self.widget.tag_bind(self.symbol, '', lambda ev, qs=self:qs.widget.enter(qs)) self.widget.tag_bind(self.label, '', lambda ev, qs=self:qs.widget.enter(qs)) self.widget.tag_bind(self.symbol, '', self.widget.leave) self.widget.tag_bind(self.label, '', self.widget.leave) # call customization hook if self.widget.init_hook: self.widget.init_hook(self) def __repr__(self): return 'Node: %s Parent: %s (%d children)' % \ (self.name, self.parent.name, len(self.subnodes)) # recursively delete subtree & clean up cyclic references def _delete(self): for i in self.subnodes: if i.open_flag and i.subnodes: # delete vertical connecting line if self.widget.lineflag: self.widget.delete(i.tree) # delete node's subtree, if any i._delete() # the following unbinding hassle is because tkinter # keeps a callback reference for each binding # so if we want things GC'd... for j in (i.symbol, i.label): for k in self.widget.tag_bind(j): self.widget.tag_unbind(j, k) for k in self.widget._tagcommands.get(j, []): self.widget.deletecommand(k) self.widget._tagcommands[j].remove(k) # delete widgets from canvas self.widget.delete(i.symbol, i.label) if self.widget.lineflag: self.widget.delete(i.line) # break cyclic reference i.parent=None # move cursor if it's in deleted subtree if self.widget.pos in self.subnodes: self.widget.move_cursor(self) # now subnodes will be properly garbage collected self.subnodes=[] # move everything below current icon, to make room for subtree # using the magic of item tags def _tagmove(self, dist): # mark everything below current node as movable bbox1=self.widget.bbox(self.widget.root.symbol, self.label) bbox2=self.widget.bbox('all') self.widget.dtag('move') self.widget.addtag('move', 'overlapping', bbox2[0], bbox1[3], bbox2[2], bbox2[3]) # untag cursor & node so they don't get moved too # this has to be done under Tk on X11 self.widget.dtag(self.widget.cursor_box, 'move') self.widget.dtag(self.symbol, 'move') self.widget.dtag(self.label, 'move') # now do the move of all the tagged objects self.widget.move('move', 0, dist) # fix up connecting lines if self.widget.lineflag: n=self while n: if len(n.subnodes): # position of current icon x1, y1=self.widget.coords(n.symbol) # position of last node in subtree x2, y2=self.widget.coords(n.subnodes[-1:][0].symbol) self.widget.coords(n.tree, x1, y1, x1, y2) n=n.parent # return list of subnodes that are expanded (not including self) # only includes unique leaf nodes (e.g. /home and /home/root won't # both be included) so expand() doesn't get called unnecessarily # thank $DEITY for Dr. Dutton's Data Structures classes at UCF! def expanded(self): # push initial node into stack stack=[(self, (self.id,))] list=[] while stack: # pop from stack p, i=stack[-1:][0] del stack[-1:] # flag to discard non-unique sub paths flag=1 # check all children for n in p.subnodes: # if expanded, push onto stack if n.open_flag: flag=0 stack.append(n, i+(n.id,)) # if we reached end of path, add to list if flag: list.append(i[1:]) return list # get full name, including names of all parents def full_id(self): if self.parent: return self.parent.full_id()+(self.id,) else: return (self.id,) # expanding/collapsing folders def toggle_state(self, state=None): if not self.open_icon: # not a expandable folder return if state == None: # toggle to other state state = not self.open_flag else: # are we already in the state we want to be? if (not state) == (not self.open_flag): return # not re-entrant # acquire mutex while self.spinlock: pass self.spinlock=1 # call customization hook if self.widget.before_hook: self.widget.before_hook(self) # if we're closed, expand & draw our subtrees if not self.open_flag: self.open_flag=1 self.widget.itemconfig(self.symbol, image=self.open_icon) # get contents of subdirectory or whatever contents = self.widget.get_contents(self) # move stuff to make room self._tagmove(self.widget.disty*len(contents)) # now draw subtree self.subnodes = [] # get current position of icon x, y = self.widget.coords(self.symbol) yp = y for i in contents: # add new subnodes, they'll draw themselves yp = yp+self.widget.disty self.subnodes.append(Node(self, i[0], i[1], i[2], i[3], x+self.widget.distx, yp, self.widget)) # the vertical line spanning the subtree if self.subnodes and self.widget.lineflag: _tval = y+self.widget.disty*len(self.subnodes) self.tree = self.widget.create_line (x, y, x, _tval) self.widget.lower(self.tree, self.symbol) # if we're open, collapse and delete subtrees elif self.open_flag: self.open_flag = 0 self.widget.itemconfig(self.symbol, image=self.closed_icon) # if we have any children if self.subnodes: # recursively delete subtree icons self._delete() # delete vertical line if self.widget.lineflag: self.widget.delete(self.tree) # find next (vertically-speaking) node n = self while n.parent: # position of next sibling in parent's list i = n.parent.subnodes.index(n)+1 if i < len(n.parent.subnodes): n = n.parent.subnodes[i] break n = n.parent if n.parent: # move everything up so that distance to next subnode is # correct x1, y1 = self.widget.coords(self.symbol) x2, y2 = self.widget.coords(n.symbol) dist = y2-y1-self.widget.disty self._tagmove(-dist) # update scroll region for new size x1, y1, x2, y2 = self.widget.bbox('all') self.widget.configure(scrollregion=(x1, y1, x2+5, y2+5)) # call customization hook if self.widget.after_hook: self.widget.after_hook(self) # release mutex self.spinlock = 0 # expand this subnode # doesn't have to exist, it expands what part of the path DOES exist def expand(self, dirs): # if collapsed, then expand self.toggle_state(1) # find next subnode if dirs: for n in self.subnodes: if n.id == dirs[0]: n.expand(dirs[1:]) break # handle mouse clicks by moving cursor and toggling folder state def click(self, event): self.widget.move_cursor(self) self.toggle_state() # cut a node and it's subtree def cut(self): # remember what was expanded, so we can re-expand on paste expand_list = self.expanded() if not self.open_flag: expand_list=None id = self.full_id() # collapse self.toggle_state(0) # delete from tree if self.parent: # move cursor safely out of the way if self.widget.pos == self: self.widget.prev() if len(self.parent.subnodes) == 1: # delete vertical connecting line # if we're the only child if self.widget.lineflag: self.widget.delete(self.parent.tree) # delete from parent's list of children self.parent.subnodes.remove(self) # move rest of tree up self._tagmove(-self.widget.disty) # break cyclic reference self.parent = None # see _delete() for why we have to do this for j in (self.symbol, self.label): for k in self.widget.tag_bind(j): self.widget.tag_unbind(j, k) for k in self.widget._tagcommands.get(j, []): self.widget.deletecommand(k) self.widget._tagcommands[j].remove(k) # delete from canvas self.widget.delete(self.symbol, self.label) if self.widget.lineflag: self.widget.delete(self.line) # update scrollbar for new height x1, y1, x2, y2 = self.widget.bbox('all') self.widget.configure(scrollregion=(x1, y1, x2+5, y2+5)) # returns a "cut_object" co=Cut_Object(self, id, expand_list) # call customization hook if self.widget.cut_hook: self.widget.cut_hook(co) return co # insert a "cut object" at the proper place # option: # 1 - insert as 1st child # 2 - insert after last child # 3 - insert as next sibling def paste(self, co, option=1): # call customization hook # this usually does the actual cut'n'paste on the underlying # data structure if self.widget.paste_hook: # if it returns false, it wasn't successful if self.widget.paste_hook(co): return # expand if necessary if option == 1 or option == 2: self.toggle_state(2) # make subnode list the right size for _tagmove() if option == 1: self.subnodes.insert(0, None) i = 0 elif option == 2: self.subnodes.append(None) i = -1 elif option == 3: i = self.parent.subnodes.index(self)+1 self.parent.subnodes.insert(i, None) # move rest of tree down self._tagmove(self.widget.disty) # place new node x, y = self.widget.coords(self.symbol) if option == 1 or option == 2: x = x+self.widget.distx y=y+self.widget.disty # create node if option == 1 or option == 2: parent = self elif option == 3: parent = self.parent n=Node(parent, co.node.name, co.node.id, co.node.open_flag, co.node.icon, x, y, self.widget) # insert into tree if option == 1 or option == 2: self.subnodes[i]=n elif option == 3: # insert as next sibling self.parent.subnodes[i] = n # expand children to the same state as original cut tree if co.state: n.expand(co.state) # return next lower visible node def next(self): n = self if n.subnodes: # if you can go right, do so return n.subnodes[0] while n.parent: # move to next sibling i = n.parent.subnodes.index(n)+1 if i < len(n.parent.subnodes): return n.parent.subnodes[i] # if no siblings, move to parent's sibling n = n.parent # we're at bottom return self # return next higher visible node def prev(self): n = self if n.parent: # move to previous sibling i = n.parent.subnodes.index(n)-1 if i >= 0: # move to last child n = n.parent.subnodes[i] while n.subnodes: n = n.subnodes[-1] else: # punt if there's no previous sibling if n.parent: n = n.parent return n #------------------------------------------------------------------------------ # default routine to get contents of subtree # supply this for a different type of app # argument is the node object being expanded # should return list of 4-tuples in the form: # (label, unique identifier, closed icon, open icon) # where: # label - the name to be displayed # unique identifier - an internal fully unique name # closed icon - PhotoImage of closed item # open icon - PhotoImage of open item, or None if not openable def get_contents(node): dirs = [] files = [] path = apply(os.path.join, node.full_id()) print "Listing dir \"%s\"" % path for filename in os.listdir(path): full = os.path.join(path, filename) name = filename folder = 0 if os.path.isdir(full): # it's a directory folder = 1 elif not os.path.isfile(full): # but it's not a file name = name+' (special)' if os.path.islink(full): # it's a link name = name+' (link to '+os.readlink(full)+')' if folder: # it's a collapsed directory dirs.append((name, filename, shut_icon, open_icon)) else: # it's not!! files.append((name, filename, file_icon, None)) dirs.sort() files.sort() return dirs+files #------------------------------------------------------------------------------ class Tree(Canvas): def __init__(self, master, rootname, rootlabel=None, openicon=None, shuticon=None, getcontents=get_contents, init=None, before=None, after=None, cut=None, paste=None, dnd=None, distx=15, disty=15, textoff=10, lineflag=1, **kw_args): global open_icon, shut_icon, file_icon # pass args to superclass apply(Canvas.__init__, (self, master), kw_args) # default images (BASE64-encoded GIF files) # we have to delay initialization until Tk starts up or PhotoImage() # complains (otherwise I'd just put it up top) if open_icon == None: open_icon = PhotoImage( data='R0lGODlhEAANAKIAAAAAAMDAwICAgP//////ADAwMAAAAAAA' \ 'ACH5BAEAAAEALAAAAAAQAA0AAAM6GCrM+jCIQamIbw6ybXNSx3GVB' \ 'YRiygnA534Eq5UlO8jUqLYsquuy0+SXap1CxBHr+HoBjoGndDpNAAA7') shut_icon = PhotoImage( data='R0lGODlhDwANAKIAAAAAAMDAwICAgP//////ADAwMAAAAAAA' \ 'ACH5BAEAAAEALAAAAAAPAA0AAAMyGCHM+lAMMoeAT9Jtm5NDKI4Wo' \ 'FXcJphhipanq7Kvu8b1dLc5tcuom2foAQQAyKRSmQAAOw==') file_icon = PhotoImage( data='R0lGODlhCwAOAJEAAAAAAICAgP///8DAwCH5BAEAAAMALAAA' \ 'AAALAA4AAAIphA+jA+JuVgtUtMQePJlWCgSN9oSTV5lkKQpo2q5W+' \ 'wbzuJrIHgw1WgAAOw==') # function to return subnodes (not very much use w/o this) if not getcontents: raise ValueError, 'must have "get_contents" function' self.get_contents = getcontents # horizontal distance that subtrees are indented self.distx = distx # vertical distance between rows self.disty = disty # how far to offset text label self.textoff = textoff # called after new node initialization self.init_hook = init # called just before subtree expand/collapse self.before_hook = before # called just after subtree expand/collapse self.after_hook = after # called at the end of the cut operation self.cut_hook = cut # called beginning of the paste operation self.paste_hook = paste # flag to display lines self.lineflag = lineflag # called at end of drag'n'drop operation self.dnd_hook = dnd # create root node to get the ball rolling if openicon: oi = openicon else: oi = open_icon if shuticon: si = shuticon else: si = shut_icon if rootlabel: self.root = Node(None, rootlabel, rootname, si, oi, 10, 10, self) else: self.root = Node(None, rootname, rootname, si, oi, 10, 10, self) # configure for scrollbar(s) x1, y1, x2, y2 = self.bbox('all') self.configure(scrollregion=(x1, y1, x2+5, y2+5)) # add a cursor self.cursor_box = self.create_rectangle(0, 0, 0, 0) self.move_cursor(self.root) # make it easy to point to control self.bind('', self.mousefocus) # # totally arbitrary yet hopefully intuitive default keybindings # stole 'em from ones used by microsoft tree control # # page-up/page-down self.bind('', self.pagedown) self.bind('', self.pageup) # arrow-up/arrow-down self.bind('', self.next) self.bind('', self.prev) # arrow-left/arrow-right self.bind('', self.ascend) # (hold this down and you expand the entire tree) self.bind('', self.descend) # home/end self.bind('', self.first) self.bind('', self.last) # space bar self.bind('', self.toggle) # scroll (in a series of nudges) so items are visible def see(self, *items): x1, y1, x2, y2=apply(self.bbox, items) while x2 > self.canvasx(0)+self.winfo_width(): old=self.canvasx(0) self.xview('scroll', 1, 'units') # avoid endless loop if we can't scroll if old == self.canvasx(0): break while y2 > self.canvasy(0)+self.winfo_height(): old=self.canvasy(0) self.yview('scroll', 1, 'units') if old == self.canvasy(0): break # done in this order to ensure upper-left of object is visible while x1 < self.canvasx(0): old=self.canvasx(0) self.xview('scroll', -1, 'units') if old == self.canvasx(0): break while y1 < self.canvasy(0): old=self.canvasy(0) self.yview('scroll', -1, 'units') if old == self.canvasy(0): break # move cursor to node def move_cursor(self, node): self.pos=node x1, y1, x2, y2 = self.bbox(node.symbol, node.label) self.coords(self.cursor_box, x1-1, y1-1, x2+1, y2+1) self.see(node.symbol, node.label) # expand given path # note that the convention used in this program to identify a # particular node is to give a tuple listing it's id and parent ids # so you probably want to use os.path.split() a lot def expand(self, path): self.root.expand(path[1:]) # soak up event argument when moused-over # could've used lambda but didn't... def mousefocus(self, event): self.focus_set() # open/close subtree def toggle(self, event=None): self.pos.toggle_state() # move to next lower visible node def next(self, event=None): self.move_cursor(self.pos.next()) # move to next higher visible node def prev(self, event=None): self.move_cursor(self.pos.prev()) # move to immediate parent def ascend(self, event=None): if self.pos.parent: # move to parent self.move_cursor(self.pos.parent) # move right, expanding as we go def descend(self, event=None): self.pos.toggle_state(1) if self.pos.subnodes: # move to first subnode self.move_cursor(self.pos.subnodes[0]) else: # if no subnodes, move to next sibling self.next() # go to root def first(self, event=None): # move to root node self.move_cursor(self.root) # go to last visible node def last(self, event=None): # move to bottom-most node n = self.root while n.subnodes: n = n.subnodes[-1] self.move_cursor(n) # previous page def pageup(self, event=None): n = self.pos j = self.winfo_height()/self.disty for i in range(j-3): n = n.prev() self.yview('scroll', -1, 'pages') self.move_cursor(n) # next page def pagedown(self, event=None): n = self.pos j = self.winfo_height()/self.disty for i in range(j-3): n = n.next() self.yview('scroll', 1, 'pages') self.move_cursor(n) # drag'n'drop support using Tkdnd # start drag'n'drop def dnd_click(self, event, node): Tkdnd.dnd_start(self, event) self.dnd_source=node # remember node we just entered, and save it as target def enter(self, node): self.dnd_target=node # we're not over a valid target def leave(self, event): self.dnd_target=None # end drag'n'drop def dnd_end(self, target, event): pass def dnd_accept(self, source, event): return self def dnd_commit(self, source, event): # destroy the move icon self.dnd_leave(None, None) # force update to get event, if any self.update() # see if non-trivial drag'n'drop occurred if self.dnd_target == None or source.dnd_source == self.dnd_target: return self.dnd_hook(source, self.dnd_target) # create drag icon def dnd_enter(self, source, event): # returns pointer position in display coordinates x, y = self.winfo_pointerxy() # translate to canvas coordinates x = self.canvasx(x)-self.winfo_rootx() y = self.canvasy(y)-self.winfo_rooty() i = source.itemcget(source.dnd_source.symbol, 'image') self.dnd_symbol=self.create_image(x, y, image=i) i = source.itemcget(source.dnd_source.label, 'text') self.dnd_label=self.create_text(x+self.textoff, y, text=i, justify='left', anchor='w' ) # destroy drag icon def dnd_leave(self, source, event): self.delete(self.dnd_symbol, self.dnd_label) # move drag icon def dnd_motion(self, source, event): # returns pointer position in display coordinates x, y=self.winfo_pointerxy() # translate to canvas coordinates x=self.canvasx(x)-self.winfo_rootx() y=self.canvasy(y)-self.winfo_rooty() self.coords(self.dnd_symbol, x, y) self.coords(self.dnd_label, x+self.textoff, y) #------------------------------------------------------------------------------ # the good 'ol test/demo code if __name__ == '__main__': import sys import string root=Tk() root.title(os.path.basename(sys.argv[0])) tree=os.sep if sys.platform == 'win32': # we could call the root "My Computer" and mess with get_contents() # to return "A:", "B:", "C:", ... etc. as it's children, but that # would just be terminally cute and I'd have to shoot myself tree='C:'+os.sep # create the control t=Tree(root, tree, rootlabel=tree, width=300) t.grid(row=0, column=0, sticky=N+E+W+S) # make expandable root.grid_rowconfigure(0, weight=1) root.grid_columnconfigure(0, weight=1) # add scrollbars sb=Scrollbar(root) sb.grid(row=0, column=1, sticky=N+S) t.configure(yscrollcommand=sb.set) sb.configure(command=t.yview) sb=Scrollbar(root, orient=HORIZONTAL) sb.grid(row=1, column=0, sticky=E+W) t.configure(xscrollcommand=sb.set) sb.configure(command=t.xview) # must get focus so keys work for demo t.focus_set() # we could do without this, but it's nice and friendly to have Button(root, text='Quit', command=root.quit).grid(row=2, column=0, columnspan=2) # expand out our current directory # note that directory case in getcwd() under win32 is sometimes broken t.expand(string.split(os.getcwd(), os.sep)) root.mainloop() forg-0.5.1.orig/AskForm.py0100644000175000017500000000370107344462645015575 0ustar jgoerzenjgoerzen# AskForm.py # Written by David Allen # Released under the terms of the GNU General Public License # $Id: AskForm.py,v 1.6 2001/04/07 19:12:44 s2mdalle Exp $ # An AskForm is essentially a conglomeration of Question objects. # # 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 string import * import GopherResponse import Question class AskForm(GopherResponse.GopherResponse): def __init__(self, askformdata=""): GopherResponse.GopherResponse.__init__(self) self.questions = [] self.setAskFormData(askformdata) return None def questionCount(self): return len(self.questions) def nthQuestion(self, nth): return self.questions[nth] def setAskFormData(self, data): self.data = data print "ASKFORM: Parsing data block:\n", data self.lines = split(self.data, "\n") for line in self.lines: line = strip(line) if line == '' or line == '.': continue try: q = Question.Question(line) except Question.QuestionException, qstr: print "Error parsing question \"%s\": %s" % (line, qstr) continue self.questions.append(q) forg-0.5.1.orig/Associations.py0100644000175000017500000001321207344462645016670 0ustar jgoerzenjgoerzen# Associations.py # $Id: Associations.py,v 1.8 2001/07/11 22:43:09 s2mdalle Exp $ # # Handles associations between file types and programs. # # 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. ######################################################################## import re import os from string import * class Associations: AssociationsException = "foo" verbose = None DELIMITER = " => " def __init__(self): self.dict = {} self.threads = 0 return None def addAssociation(self, suffix, pgm): """Adds an association to the list. suffix holds the file extension, and pgm holds the name of the executing program. Example is suffix=.jpg and pgm=gimp $1""" if suffix[0] == '.': suffix = suffix[1:] self.dict[suffix] = pgm return None def save(self, filename): """Saves associations to filename""" return self.writeToFile(filename) def writeToFile(self, filename): """Writes text representation to filename that can be loaded later""" fp = open(filename, "w") lines = ["FORG associations. This is the mechanism that allows the", "FORG to launch particular programs when dealing with", "different file extensions. The format is like this:", "file_suffix%sprogram_to_launch" % self.DELIMITER, "where file_suffix is something such as \".jpg\", etc.", "program_to_launch is the command line of the program that", "you want launched. You may use $1 to represent the file", "so to launch a HTML file, you might use:", ".html%s/usr/X11/bin/netscape $1" % self.DELIMITER] map(lambda line,f=fp: f.write("# %s\n" % line), lines) for key in self.dict.keys(): fp.write("%s%s%s\n" % (key, self.DELIMITER, self.dict[key])) fp.flush() fp.close() return 1 def loadFromFile(self, filename): """Loads instance of Associations from filename""" fp = open(filename, "r") self.dict = {} for line in fp.readlines(): line = strip(line) if len(line) > 0 and line[0] == '#': continue # Skip comments. try: [key, value] = split(line, self.DELIMITER, 2) self.addAssociation(key, value) except: print "Error parsing line in associations file: %s" % line fp.close() return 1 def isEmpty(self): return len(self.dict.keys()) == 0 def getFileTypes(self): """Returns the file suffixes the Association list knows of""" return self.dict.keys() def getProgramString(self, key): try: ans = self.dict[key] return ans except KeyError: return None def getTmpFilename(self, suffix, *args): return foo + "." + suffix return None def removeAssociation(self, suffix): try: del(self.dict[suffix]) except KeyError: pass def getAssociation(self, filename): """Given a particular filename, find the correct association for it, if any.""" # Turn it into a real filename so some programs won't go stupid # on us and try to look up a partial URL in a filename, particularly # with cache filenames. # print "Finding assoc for %s" % filename filename = "." + os.sep + filename matchFound = None ind = len(filename)-1; while not matchFound and ind != -1: str = filename[ind+1:] assoc = None try: assoc = self.dict[str] except: pass ind = rfind(filename, ".", 0, ind) if assoc: matchFound = 1 if ind == -1 or not matchFound: # print "Couldn't find association for this filetype." return None # print "Found assoc %s for filename %s" % (assoc, filename) return assoc def applyAssociation(self, filename, assoc=None): """Given a filename and an association, execute the helper program in order to properly process the file.""" if assoc == None: assoc = self.getAssociation(filename) if assoc == None or assoc == '': raise AssociationsException, "No association found." # Assoc holds the program name assoc = re.sub("$1", "\"" + filename + "\"", assoc, 1) fp = os.popen(assoc) print "Process dump: %s" % assoc try: while 1: line = fp.readline() if line == '': break; print line except: print "Process %s exited with an exception." % assoc return None print "Process \"%s\" finished up and exited." % assoc return 1 forg-0.5.1.orig/AssociationsEditor.py0100644000175000017500000002026707344462645020047 0ustar jgoerzenjgoerzen# AssociationsEditor.py # $Id: AssociationsEditor.py,v 1.8 2001/07/06 03:06:41 s2mdalle Exp $ # Written by David Allen # This pops up a dialog box and allows the user to associate file name # extensions with various programs to run. # # It returns an Associations object, and can optionally take one as an # argument. # # 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. ######################################################################## import Associations from gopher import * from Tkinter import * import Pmw from string import * class AssociationsEditor: DELIMITER = Associations.Associations.DELIMITER def __init__(self, parent, baseAssoc=Associations.Associations()): self.parent = parent self.dialog = Pmw.Dialog(parent, title='Edit Associations', buttons=('OK', 'Cancel'), defaultbutton='OK', command=self.dispatch) self.assoc = baseAssoc self.frame = self.dialog.interior() self.frame.pack(expand=1, fill='both') self.left_side = Frame(self.frame) self.middle = Frame(self.frame) self.right_side = Frame(self.frame) self.left_side.pack(side='left', expand=1, fill='both') self.middle.pack(side='left', expand=1, fill='both') self.right_side.pack(side='left', expand=1, fill='both') # Left side widgets inputbox = Frame(self.left_side) inputbox.grid(row=0, col=0, columnspan=2, rowspan=2, sticky=W) # File extension entry box and label. ftlabel = Label(inputbox, text='File extension:') self.ftEntry = Entry(inputbox, width=6) ftlabel.grid(row=0, col=0, sticky=W) self.ftEntry.grid(row=0, col=1, sticky=W) # Application entry box and label applabel = Label(inputbox, text='Application:') self.appEntry = Entry(inputbox, width=30) applabel.grid(row=1, col=0, sticky=W) self.appEntry.grid(row=1, col=1, sticky=W) # Instruction group box. group = Pmw.Group(self.left_side, tag_text='Instructions:') group.grid(row=2, column=0, rowspan=2, columnspan=2, sticky=W) instr1 = Label(group.interior(), text='When entering in programs associated with files,') instr2 = Label(group.interior(), text='Use $1 to represent the file being launched') instr3 = Label(group.interior(), text='Filename extensions may be provided with or') instr4 = Label(group.interior(), text='dots. I.e. ".html" is the same as "html"') instr5 = Label(group.interior(), text="Example: .html might be associated with") instr6 = Label(group.interior(), text="netscape $1") instr7 = Label(group.interior(), text=" ") instr8 = Label(group.interior(), text="Extensions are case-sensitive") instr1.pack(side='top') instr2.pack(side='top') instr3.pack(side='top') instr4.pack(side='top') instr5.pack(side='top') instr6.pack(side='top') instr7.pack(side='top') instr8.pack(side='top') # Middle widgets self.addAssociationButton = Button(self.middle, text='Add', command=self.add) self.removeAssociationButton = Button(self.middle, text='Remove', command=self.remove) self.setDefaultsButton = Button(self.middle, text='Defaults', command=self.resetAssociations) # self.addAssociationButton.pack(side='top', expand=1, fill='both') # self.removeAssociationButton.pack(side='bottom', expand=1, fill='both') self.addAssociationButton.grid(row=0, col=0, sticky='NEW') self.removeAssociationButton.grid(row=1, col=0, sticky='NEW') self.setDefaultsButton.grid(row=2, col=0, sticky='NEW') # Right side widgets self.associationList = Pmw.ScrolledListBox(self.right_side, hscrollmode='dynamic', vscrollmode='static', labelpos='nw', dblclickcommand=self.reIns, label_text='Associations:') self.associationList.pack(expand=1, fill='both') self.setAssociations(self.assoc) # self.dialog.activate() # Make the dialog modal so the user can't # # mess with things in the other window. def resetAssociations(self, *args): self.setAssociations(self.assoc) def reIns(self, *args): selected = self.associationList.getcurselection() selected = selected[0] index = find(selected, self.DELIMITER) extension = selected[0:index] pgm = selected[index+len(self.DELIMITER):] self.ftEntry.delete(0, 'end') self.appEntry.delete(0, 'end') self.ftEntry.insert('end', extension) self.appEntry.insert('end', pgm) return None def extensionToAssociationExtension(self, ext): if len(ext) > 0: if ext[0] == '.': return ext[1:] else: return ext return ext def add(self, *args): extension = self.extensionToAssociationExtension(self.ftEntry.get()) pgm = self.appEntry.get() # Set the contents of the entry boxes back to nothing so the user # doesn't have to delete the contents before adding another association self.appEntry.delete(0, 'end') self.ftEntry.delete(0, 'end') str = extension + self.DELIMITER + pgm items = self.associationList.get() addItem = 1 # Check to see if this entry is already in there somewhere. for x in range(0, len(items)): item = items[x] # If they have the same extension... if extension == item[0:len(extension)]: print "Replacing \"%s\"" % item # Remove it from the list. items = items[0:x-1] + (str,) + items[x+1:] addItem = None break if addItem: items = items + (str,) self.associationList.setlist(items) return None def remove(self, *args): self.associationList.delete('active') return None def setAssociations(self, assoc): list = () for assocKey in assoc.getFileTypes(): str = assocKey + self.DELIMITER + assoc.getProgramString(assocKey) list = list + (str,) self.associationList.setlist(list) return None def getAssociations(self, *args): self.assoc = Associations.Associations() for item in self.associationList.get(): print "Got item %s" % item index = find(item, self.DELIMITER) extension = item[0:index] pgm = item[index+len(self.DELIMITER):] self.assoc.addAssociation(extension, pgm) return self.assoc def dispatch(self, button): if button == 'OK': assocs = self.getAssociations() self.parent.setAssociations(assocs) self.dialog.destroy() # Grab data and put it into an Assocations object. self.dialog.destroy() return None forg-0.5.1.orig/BUGS0100644000175000017500000000221407344462645014342 0ustar jgoerzenjgoerzenCurrent known bugs in FORG: - Some dialog menus have to have their titlebar clicked or be resized before they will actually appear. This looks like a Pmw thing. - Find facility for text files works, but it doesn't show the user the result, so for all practical purposes it doesn't work. :) - Find facility on directories works, but if the item found wasn't on the screen, you may have to scroll down to find it. - Random python segfaults seem to happen occasionally due to Pmw. I cannot trace this. If you are able to get a core file out of python and this happens to you, please email it to me. (Along with info on the version of python you're running) - Sometimes (rarely) windows will not appear, and there will be an unhandled exception thrown through Pmw's convoluted scrolling code. I'm having a hell of a time tracing this too, but it should be done with eventually. - Under some strange circumstances, the program may crash when launching an external application to deal with a file. - The "STOP" button doesn't work. - If the program is abnormally killed, it may leave spawned processes behind, despite its best efforts not to. forg-0.5.1.orig/Bookmark.py0100644000175000017500000003401207344462645015777 0ustar jgoerzenjgoerzen# Bookmark.py # $Id: Bookmark.py,v 1.14 2001/07/11 22:43:09 s2mdalle Exp $ # Written by David Allen # This is a subclass of GopherResource and is used in the Bookmark # management system of the FORG. # # 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 string import * from Tkinter import * import GopherResource import ListNode import List import utils import xmllib BookmarkFactoryException = "Error" BookmarkException = "Error" BookmarkMenuException = "Error" class Bookmark(GopherResource.GopherResource): def __init__(self, res=None): GopherResource.GopherResource.__init__(self) if res != None: # Copy data from the resource to this bookmark. self.setName(res.getName()) self.setHost(res.getHost()) self.setPort(res.getPort()) self.setLocator(res.getLocator()) self.setType(res.getTypeCode()) return None def toXML(self, fp, indentlevel): """Returns an XML representation of the object. Callers should use an indentlevel of 0 since this is called recursively. fp is the file pointer the data should be stored in.""" if self.verbose: print "Bookmark.toXML()" fp.write(utils.indent(indentlevel) + "\n") fp.write(utils.indent(indentlevel+1) + "" + self.getName() + "\n") fp.write(utils.indent(indentlevel) + "\n") return 1 def getURL(self): return self.toURL() def toData(self): return "%s !! gopher://%s:%s/%s" % (self.getName(), self.getHost(), self.getPort(), self.getLocator()) def __str__(self): return self.toString() def __repr__(self): return self.toString() def toString(self): if self.getName() != '': return "%s: %s" % (self.getHost(), self.getName()) elif self.getLocator() == '/' or self.getLocator() == '': return "%s Root" % self.getHost() else: return "%s:%s %s" % (self.getHost(), self.getPort(), self.getLocator()) # Not specific to the bookmark class methods... def parseBookmark(data): data = strip(data) items = split(data, "!! ", 2) if len(items) < 2: print "***Bookmark parse error: Items is: %s" % join(items,"::") return None bmark = Bookmark() try: items[1] = strip(items[1]) bmark.setURL(items[1]) except Exception, estr: print "Returning NONE because I couldn't parse the URL: %s" % estr return None bmark.setName(items[0]) return bmark # Subclass ListNode just for naming purposes so BookmarkMenuNodes can be # dealt with inside of BookmarkMenus class BookmarkMenuNode(ListNode.ListNode): pass # BookmarkMenu is to organize heirarchical menus of bookmarks which will be # editable by the user. class BookmarkMenu(List.List): verbose = None def __init__(self, menuName=" "): List.List.__init__(self) self.menuName = menuName return None def getName(self): if strip(self.menuName) > 0: return self.menuName else: self.setName("Bookmarks") return self.menuName return "Error fetching name" def setName(self, newname): self.menuName = newname return self.menuName def toString(self): return "BookmarkMenu: \"%s\"" % self.getName() def __str__(self): return self.toString() def __repr__(self): return self.toString() def insert(self, item, truncate=0): """Overrides the insert method to always pass a truncate argument of 0 so the list is never truncated on insertions.""" return List.List.insert(self, item, 0) def addSubmenu(self, menu): """Adds menu as a submenu of this menu""" if menu.__class__ != BookmarkMenu and menu.__class__ != Bookmark: raise Exception, "Cannot add a non-Bookmark/Menu as submenu" return self.insert(BookmarkMenuNode(menu)) def toXML(self, fp, indentlevel): """Returns an XML representation of this object. This is called recursively, so if you are calling this for the first time, use an indentlevel of 0. fp holds the file pointer to write the data into""" if self.verbose: print "BookmarkMenu.toXML()" il = indentlevel i = utils.indent fp.write(i(il) + "\n") fp.write(i(il+1) + "" + self.getName() + "\n") def fn(item, fp=fp, il=(il+1)): data = item.getData() # It doesn't matter which kind it is, whether it's a # Bookmark or a BookmarkMenu since they both have toXML() methods # and they'll take it from here. If it's a BookmarkMenu, this # will happen recursively. data.toXML(fp, il) return data.getName() # Apply the above function to each item in the menu self.traverse(fn) fp.write(i(il) + " \n" % self.getName()) return None def getTkMenu(self, parent_widget, callback): """Return a Tk Menu object which is suitable for being added to other submenus. parent_widget is the menu you will add it to, and callback is a function that takes exactly one argument. That argument is the Bookmark object that the user-clicked item represents.""" # This is the menu we will return. m = Menu(parent_widget) # Create a subfunction to deal with each item in the list. # This way since this object already extends List and we already have # a traverse function, we can go through the entire list in order def fn(listitem, cb=callback, addTo=m): # Each item in the list is a ListNode, not a Bookmark, so get the # data out of the ListNode. data = listitem.getData() def real_callback(item=data, oldCallback=cb): """This is a way of converting a callback that takes no arguments into one that takes two for our purposes. The user passes in a callback function that takes only one parameter (the resource you want to go to) and this is the actual function that is bound to the action, which calls the user defined function with the appropriate argument""" return oldCallback(item) try: # If it's a menu, then add the submenu recursively. if data.__class__ == BookmarkMenu: addTo.add_cascade(label=data.getName(), menu=data.getTkMenu(addTo, cb)) else: # Otherwise add a regular command into the menu. addTo.add_command(label=data.getName(), command=real_callback) except: pass return data.getName() # Apply the above to each item in the list, adding menu items and # submenus where needed. self.traverse(fn) # Don't care about the return results. return m class BookmarkFactory(xmllib.XMLParser): verbose = None def __init__(self): xmllib.XMLParser.__init__(self) self.menu = None self.lastTag = '' self.accumulator = '' self.currentMenu = None self.currentBmrk = None self.folders = [] return None def writeXML(self, fp, menu): """Writes an XML representation of menu to fp""" # Header-type stuff. fp.write("\n") fp.write("\n\n") fp.write("\n\n"); fp.write("\n") menu.toXML(fp, 1) fp.write("\n") fp.flush() return 1 def getMenu(self): """Menu object accessor""" return self.menu def setMenu(self, menu): """Menu object mutator""" self.menu = menu return self.menu def handle_data(self, data): """Necessary overriden method for XML parsing - when a chunk of data comes in, this is what is done with it.""" self.accumulator = self.accumulator + data def syntax_error(self, message): """Necessary overridden method for XML parsing - handles syntax errors when they occur""" print "****Error parsing XML: ", message return None def unknown_starttag(self, tag, attrs): """Necessary overriden method for XML parsing - handles unknown start tags""" print "****Error parsing XML: Unknown tag \"%s\"" % tag return None def unknown_endtag(self, tag): """Necessary overridden method for XML parsing - handles unknown end tags""" print "****Error parsing XML: Unknown ending tag \"%s\"" % tag return None def unknown_charref(self, ref): """Necessary overridden method for XML parsing - handles unknown charrefs""" print "****Error parsing XML: uknown charref \"%s\"" % ref return None def unknown_entityref(self, ref): """Necessary overridden method for XML parsing - handles unknown entity references""" print "****Error parsing XML: unknown entityref \"%s\"" % ref return None def start_xbel(self, attrs): self.lastTag = "xbel" if self.verbose: print "" return None def start_folder(self, attrs): self.currentMenu = BookmarkMenu() self.folders.append(self.currentMenu) self.lastTag = "folder" if self.verbose: print "Creating new folder" return None def start_bookmark(self, attrs): self.currentBmrk = Bookmark() self.lastTag = "bookmark" if self.verbose: print "Setting URL to be ", attrs['href'] try: self.currentBmrk.setURL(attrs['href']) except KeyError: print "**** Error parsing XML: bookmark is missing 'href'" except Exception, errstr: print "**** Parse error: Couldn't parse %s: %s" % (attrs['href'], errstr) self.currentBmrk = None return None if self.verbose: print "Creating new bookmark" return None def start_title(self, attrs): if self.lastTag == 'xbel': self.currentMenu.setName("Bookmarks") else: self.accumulator = '' return None def end_title(self): self.accumulator = strip(self.accumulator) if self.lastTag == 'xbel' or self.lastTag == 'folder': if self.verbose: print 'Setting menu name: ', self.accumulator self.currentMenu.setName(self.accumulator) elif self.lastTag == 'bookmark': if self.verbose: print "Setting bmark name: ", self.accumulator self.currentBmrk.setName(self.accumulator) else: if self.verbose: print("****Error parsing XML: Unknown lastTag %s" % self.lastTag) self.accumulator = '' return None def end_bookmark(self): if self.verbose: print "Inserting new bmark" if self.currentBmrk: self.currentMenu.insert(BookmarkMenuNode(self.currentBmrk)) else: print "**** Error parsing XML: could not insert invalid bookmark." self.lastTag = "/bookmark" return None def end_folder(self): try: finished_folder = self.folders.pop() except IndexError: print "****Error parsing XML: without " return None if self.verbose: print "Finishing up folder: %s" % finished_folder.getName() if len(self.folders) > 0: self.currentMenu = self.folders[len(self.folders)-1] if self.verbose: print("Adding submenu \"%s\" to \"%s\"" % (finished_folder.getName(), self.currentMenu.getName())) self.currentMenu.addSubmenu(finished_folder) else: # The stack is empty - assign the main menu to be this item # here. if self.verbose: print "Finished toplevel folder." self.menu = finished_folder return None def end_xbel(self): if self.verbose: print "/XBEL" if len(self.folders) > 0: print "**** Error parsing XML: premature element." def parseResource(self, fp): for line in fp.read(): self.feed(line) self.close() return self.menu forg-0.5.1.orig/BookmarkEditor.py0100644000175000017500000003264007344462645017153 0ustar jgoerzenjgoerzen# BookmarkEditor.py # Written by David Allen # # 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 Tkinter import * import Pmw import Tree import Dialogs import Bookmark import os from string import * import GopherObject import GopherResource import Options def traversal_function(node): if node.__class__ != Tree.Node: print "NODE CLASS: %s" % node.__class__ return None else: bm = node.id if bm.__class__ == Bookmark.BookmarkMenu: menu = Bookmark.BookmarkMenu() menu.setName(bm.getName()) # Visit each of the children. Note that this is children as in what # is displayed on the screen after the user has edited the bookmarks # with the editor. This is NOT the children that are listed in the # actual BookmarkMenu's data structure, since that may be wrong after # the user edits. for subnode in node.subnodes: rval = traversal_function(subnode) if not rval: print "**** That's weird. rval ain't." continue # The items are one of two things - BookmarkMenu's or # BookmarkMenuNode's. Figure out which, and add it appropriately. # Note that you can't insert a submenu, you have to use addSubmenu # which is the reason for this conditional. if rval.__class__ == Bookmark.BookmarkMenu: print "Adding submenu: %s" % rval.getName() menu.addSubmenu(rval) else: # print "Adding ITEM: %s" % rval.__class__ # print "Adding ITEM: %s %s" % (rval.__class__, rval.getName()) menunode = Bookmark.BookmarkMenuNode(rval) menu.insert(menunode) # Return the generated menu to be added return menu else: # No children...It's a BookmarkMenuNode return bm class BookmarkEditor(Toplevel): def __init__(self, bmtree, ondestroy=None): Toplevel.__init__(self) self.title("Edit Bookmarks...") # Callback to be fired off when this widget is destroyed. self.ondestroy = ondestroy # If user tries to close the window, call self.destroy self.protocol("WM_DELETE_WINDOW", self.destroy) self.make_menus() self.config(menu=self.menu) self.mainBox = Frame(self) self.mainBox.pack(fill='both', expand=1) self.tree = Tree.Tree(self.mainBox, bmtree, rootlabel="Bookmarks", lineflag=0) self.tree.grid(row=0, col=0, sticky='NSEW') # Make expandable self.mainBox.grid_rowconfigure(0, weight=1) self.mainBox.grid_columnconfigure(0, weight=1) self.vscrollbar = Scrollbar(self.mainBox, orient=VERTICAL) self.vscrollbar.grid(row=0, column=1, sticky='NS') self.tree.configure(yscrollcommand=self.vscrollbar.set) self.vscrollbar.configure(command=self.tree.yview) self.hscrollbar = Scrollbar(self.mainBox, orient=HORIZONTAL) self.hscrollbar.grid(row=1, column=0, sticky='EW') self.tree.configure(xscrollcommand=self.hscrollbar.set) self.hscrollbar.configure(command=self.tree.xview) # must get focus so keys work for demo self.tree.focus_set() # This is CRITICAL. Make sure this is done for several reasons: first # it expands the tree so the user can see the whole thing when the # editor pops open. Second, unless items in the tree are expanded, # their data elements aren't associated with the tree branches, so in # order for the bookmarks to save properly, everything must have been # expanded at one point or another, and this just ensures that. self.tree.expandAll() return None def destroy(self, *args): """User closed the window. Prompt for saving the bookmarks to disk.""" print "BookmarkEditor::destroy()" def cb(buttonName, self=self): print "Confirm callback: ", buttonName if buttonName == 'OK': self.save() if self.ondestroy: # Call the destroy callback specified by our parent if it's # present. self.ondestroy() # Call superclass method to actually destroy the window. Toplevel.destroy(self) return None # Create a confirmation dialog box self._confirmDialog = Pmw.MessageDialog(self, message_text="Save Bookmarks?", buttons=('OK', 'Cancel'), defaultbutton='OK', title='Save Bookmarks?', command=cb) return None def getActive(self): i = self.tree.getActive() print "Active is %s class %s" % (i, i.__class__) def make_menus(self, *args): self.menu = Menu(self) self.filemenu = Menu(self.menu) self.filemenu.add_command(label="Save", command=self.save) self.filemenu.add_command(label="Create Folder", command=self.createFolder) self.filemenu.add_command(label="Delete Folder", command=self.deleteFolder) self.filemenu.add_command(label="Add a Bookmark", command=self.addBookmark) self.filemenu.add_command(label="Close", command=self.destroy) self.editmenu = Menu(self.menu) self.editmenu.add_command(label="Cut", command=self.cut) self.editmenu.add_command(label="Copy", command=self.copy) self.editmenu.add_command(label="Paste", command=self.paste) self.editmenu.add_command(label="Delete", command=self.delete) self.testmenu = Menu(self.menu) self.testmenu.add_command(label="Get Active", command=self.getActive) self.menu.add_cascade(label="File", menu=self.filemenu) self.menu.add_cascade(label="Edit", menu=self.editmenu) self.menu.add_cascade(label="Test", menu=self.testmenu) return None def save(self, *args): # data_tree is a Node object, not a BookmarkMenu self.tree.expandAll() # Expand all nodes so data is present in ADT's data_tree = self.tree.getTree() # Take the id attribute out of each Node and string them together. # things may have been moved around, so the links inside the data # structures are no good, only copy. bmarks = traversal_function(data_tree) prefs_dir = Options.program_options.getOption('prefs_directory') filename = prefs_dir + os.sep + "bookmarks" factory = Bookmark.BookmarkFactory() try: fp = open(filename, "w") factory.writeXML(fp, bmarks) fp.close() except IOError, errstr: e = "Could not save bookmarks to\n%s:\n%s" % (filename, errstr) d = Dialogs.ErrorDialog(self, e, "Error Saving Bookmarks") def insertBookmark(self, bookmark): original_cut_buffer = self.tree.getCutBuffer() p = self.tree.getActive().parent newbm = Tree.Node(parent=None, name=bookmark.getName(), id=bookmark, closed_icon=Tree.Icons.FILE_ICON, open_icon=None, x=10, y=10, parentwidget=self.tree) co = Tree.Cut_Object(newbm, newbm.full_id(), None) # Set the cutbuffer to be the custom folder we just created. self.tree.setCutBuffer(co) # Paste it into the heirarchy self.tree.paste() # Set the cut buffer back to its original position. self.tree.setCutBuffer(original_cut_buffer) return None def addBookmark(self): # Prompt the user for info. After the info is completed, # insertBookmark will be called with a GopherResponse object as an # argument. dialog = Dialogs.NewBookmarkDialog(parentwin=self, cmd=self.insertBookmark) return None def createFolder(self, folderName=None): # Basically, just create a Cut_Object, and then call the paste method # to insert it into the tree. Make sure to preserve the cut object # that the tree is working with. if not folderName: self.__newFolderDialog = Dialogs.NewFolderDialog(self, self.createFolder) return None original_cut_buffer = self.tree.getCutBuffer() bmarkmenu = Bookmark.BookmarkMenu() bmarkmenu.setName(folderName) # We have to create a Node to insert into the tree, just like all other # nodes in the tree. Since we're pasting it into the heirarchy and # the parent is going to change, we don't need to specify one. 'id' # is the data associated with the Node object. folder_node = Tree.Node(parent=None, name=folderName, id=bmarkmenu, closed_icon=Tree.Icons.SHUT_ICON, open_icon=Tree.Icons.OPEN_ICON, x=10, y=10, parentwidget=self.tree) # Create a Cut_Object. This is done just as it is done in the # Node.cut() method in Tree.py - we have to use our custom created # node in order to create this Cut_Object. co = Tree.Cut_Object(folder_node, folder_node.full_id(), 1) # Set the cutbuffer to be the custom folder we just created. self.tree.setCutBuffer(co) # Paste it into the heirarchy self.tree.paste() # Set the cut buffer back to its original position. self.tree.setCutBuffer(original_cut_buffer) return None def deleteFolder(self, *args): if not self.tree.getActive().isFolder(): errstr = "Error:\nThe selected item\nisn't a folder." err = Dialogs.ErrorDialog(self, errstr, "Error") else: cutBuffer = self.tree.getCutBuffer() # Delete the item using the cut operation self.tree.cut() # Restore the old cutbuffer. Normally after deleting whatever we # deleted would be in the cut buffer, but since we're deleting and # not cutting, it need not be in the cutbuffer. self.tree.setCutBuffer(cutBuffer) return None def delete(self, *args): """Deletes the currently selected node out of the tree.""" a = self.tree.getActive() if a == self.tree.getRoot(): d = Dialogs.ErrorDialog(self, "Error:\nYou cannot delete the root.") return None # Get the old cut buffer cutbuf = self.tree.getCutBuffer() # Cut the item out (which overwrites the cutbuffer) self.cut() # Set the old cutbuffer back, meaning that the deleted item is gone # forever. self.tree.setCutBuffer(cutbuf) return None def cut(self, *args): """Cuts the selected node out of the tree.""" a = self.tree.getActive() if a == self.tree.getRoot(): # Bad mojo. You can't cut the root node. d = Dialogs.ErrorDialog(self, "Error:\nYou cannot cut the root element") return None return self.tree.cut() def copy(self, *args): a = self.tree.getActive() if a == self.tree.getRoot(): # Nope, can't copy the root. # Should we allow this? They couldn't paste it in at the same # level, but they could paste it in at a sublevel. I don't # know why anybody would want to have a subfolder, but they # might. So let's flag this with a FIXME with the implementation # note that if you're going to allow this you can't use the # cut() method to do it. d = Dialogs.ErrorDialog(self, "Error:\nYou cannot copy the root element") return None self.cut() return self.paste() def paste(self, *args): if self.tree.getCutBuffer(): return self.tree.paste() else: d = Dialogs.ErrorDialog(self, "There is no active\nbookmark to paste") forg-0.5.1.orig/COPYING0100644000175000017500000004306607344462645014724 0ustar jgoerzenjgoerzen GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc. 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Library General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS Appendix: How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) 19yy 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. Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) 19yy name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Library General Public License instead of this License. forg-0.5.1.orig/Cache.py0100644000175000017500000002454207344462645015244 0ustar jgoerzenjgoerzen# Cache.py # $Id: Cache.py,v 1.14 2001/07/14 22:50:28 s2mdalle Exp $ # Written by David Allen # Released under the terms of the GNU General Public License # # Handles cache-file related operations. # # 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. ############################################################################ import os import utils import string from gopher import * from Tkinter import * import GopherResponse import Pmw import Options CacheException = "Error" class Cache: verbose = None def __init__(self, *args): self._d = None return None def getCacheDirectory(self): try: dir = Options.program_options.getOption('cache_directory') return os.path.abspath(dir) except: return os.path.abspath("cache") def getCachePrefix(self): try: dir = Options.program_options.getOption('cache_prefix') return os.path.abspath(dir) except: return os.path.abspath("cache%scache" % os.sep) def getCacheStats(self): cdir = self.getCacheDirectory() [filecount, totalBytes, dircount] = utils.summarize_directory(cdir) kbcount = float(totalBytes)/float(1024) mbcount = kbcount/float(1024) kbcount = "%0.2fKB" % kbcount mbcount = "%0.2fMB" % mbcount datasize = "There is %s (%s) of data" % (kbcount, mbcount) fdcount = "in %s files (%s directories total)" % (filecount, dircount) closer = "underneath %s" % cdir return "%s\n%s\n%s" % (datasize, fdcount, closer) def emptyCache(self, parentTk): pref = self.getCachePrefix() self.dialog = Pmw.Dialog(parent=parentTk, title="Really delete cache?", buttons=('OK', 'Cancel'), defaultbutton='Cancel', command=self.__confirmDelete) fr = Frame(self.dialog.interior()) fr.pack(side='top', fill='both', expand=1) lab1 = Label(fr, text="Are you sure you want to empty the cache?") lab2 = Label(fr, text="WARNING: THIS WILL DELETE ALL FILES AND") lab3 = Label(fr, text="ALL SUBDIRECTORIES IN YOUR CACHE DIRECTORY,") lab4 = Label(fr, text="%s" % pref) lab5 = Label(fr, text=" ") lab6 = Label(fr, text="Continue?") lab1.grid(row=1, col=0, columnspan=5) lab2.grid(row=2, col=0, columnspan=5) lab3.grid(row=3, col=0, columnspan=5) lab4.grid(row=4, col=0, columnspan=5) lab5.grid(row=5, col=0, columnspan=5) lab6.grid(row=6, col=0, columnspan=5) # self.dialog.activate() def deleteCacheNoPrompt(self, *args): """Delete the cache without asking the user. Do not do this unless you know what you're doing.""" return self.__deleteCache() def __confirmDelete(self, button): if button != 'OK': self.dialog.destroy() return None self.dialog.destroy() # Destroy the dialog anyway self.__deleteCache() # Clean the files out return None def __deleteCache(self): pref = self.getCachePrefix() if not utils.dir_exists(pref): raise CacheException, "Cache prefix %s doesn't exist." % pref cache_directories = os.listdir(pref) # I've been told that there's a shell utility module that does this # probably safer and better, but I'm not sure where it is, or whether # or not it's portable to crappy no-name 32 bit operating systems # out of Redmond, Washington. for item in cache_directories: item = "%s%s%s" % (pref, os.sep, item) if os.path.isdir(item): print "Recursively deleting \"%s\"" % item utils.recursive_delete(item) else: print "Eh? \"%s\" isn't a directory. That's odd..." % item def isInCache(self, resource): """Takes a resource, and returns true if the resource seems to have an available cache file associated with it, and None otherwise.""" pref = self.getCachePrefix() filename = resource.toCacheFilename() if pref[len(pref)-1] != os.sep and filename[0] != os.sep: # When joining together, separate paths with os.sep pref = "%s%s" % (pref, os.sep) # Now join together, and they will be properly joined. filename = pref + filename try: info = os.stat(os.path.abspath(filename)) return [os.path.abspath(filename), info[6]] except OSError: return None return None def uncache(self, resource): """Takes a resource, and returns either None if the given resource is not cached on disk, or it returns a GopherResponse corresponding to what would have been gotten if it had been fetched from the server.""" pref = self.getCachePrefix() file = resource.toCacheFilename() if pref[len(pref)-1] != os.sep and file[0] != os.sep: # When joining together, separate paths with os.sep pref = "%s%s" % (pref, os.sep) filename = pref + file try: # See if the file exists... tuple = os.stat(filename) if self.verbose: print "File %s of size %d exists." % (filename, tuple[6]) except OSError: # The file doesn't exist, we can't uncache it. return None print "===> Uncaching \"%s\"" % filename resp = GopherResponse.GopherResponse() resp.setType(resource.getTypeCode()) resp.setHost(resource.getHost()) resp.setPort(resource.getPort()) resp.setLocator(resource.getLocator()) resp.setName(resource.getName()) try: fp = open(filename, "r") # Consider reworking this somehow. Slurp the entire file into # buffer. buffer = fp.read() fp.close() try: resp.parseResponse(buffer) resp.setData(None) # print "Loaded cache is a directory entry." except: # print "Loaded cache is not a directory." resp.setData(buffer) # Got it! Loaded from cache anyway... # print "UNCACHE found data for use." return resp except IOError, errstr: raise CacheException, "Couldn't read data on\n%s:\n%s" % (filename, errstr) # We failed. Oh well... return None def cache(self, resp, resource): """Takes a GopherResponse and a GopherResource. Saves the content of the response to disk, and returns the filename saved to.""" if resource.isAskType(): # Don't cache ASK blocks. This is because if you do, the program # will interpret it as data, and put the question structure inside # a text box. Plus, since these may be dynamic, caching could # lead to missing out on things. raise CacheException, "Do not cache AskTypes. Not a good idea." basedir = self.getCachePrefix() basefilename = resource.toCacheFilename() # Problem - basedir is our base directory, but basefilename contains # trailing filename info that shouldn't be part of the directories # we're creating. filenamecopy = basefilename[:] ind = string.rfind(filenamecopy, os.sep) # Chop off extra info so "/home/x/foobar" becomes "/home/x/foobar" # this is because make_directories will otherwise create foobar as # a directory when it's actually a filename filenamecopy = filenamecopy[0:ind] # Create the directory structure where necessary utils.make_directories(filenamecopy, basedir) basedirlastchar = basedir[len(basedir)-1] if basedirlastchar == os.sep: filename = "%s%s" % (basedir, basefilename) else: filename = "%s%s%s" % (basedir, os.sep, basefilename) # print "Cache: caching data to \"%s\"" % filename try: fp = open(filename, "w") if resp.getData() == None: # This is a directory entry. response_lines = resp.getResponses() # Each response line is a GopherResource # write them as if it was a file served by the gopher server. # that way it can be easily reparsed when loaded from the # cache. for response_line in response_lines: fp.write(response_line.toProtocolString()) # write the string terminator. This isn't really needed # since it isn't data, but it helps fool our other objects # into thinking that it's dealing with data off of a socket # instead of data from a file. So do it. fp.write("\r\n.\r\n") else: fp.write(resp.getData()) fp.flush() fp.close() except IOError, errstr: # Some error writing data to the file. Bummer. raise CacheException, "Couldn't write to\n%s:\n%s" % (filename, errstr) # Successfully wrote the data - return the filename that was used # to save the data into. (Absolute path) return os.path.abspath(filename) forg-0.5.1.orig/ChangeLog0100644000175000017500000000575607344462645015447 0ustar jgoerzenjgoerzenChanges since version 0.5 - Fixed short read for gopher servers that misreport the content lengths of documents. FORG now totally disregards content lenghts since they are so unreliable. Changes since version 0.4.1 - Added the 'delete' feature in the Bookmark Editor. - Bookmarks now reload after the editor widget is destroyed. - Fixed "inactive bookmark menu" problem when reloading bookmark menu. - Added many more docstrings to the source. - Fixed URL parsing bug on startup. Previously the program mishandled fully qualified URLs as arguments, now it doesn't. - Added option: delete cache on exit - Added option: home. - Added "Home" button on toolbar and "Set this site as my home site" option in the options menu. Changes since version 0.4: - Fixed Makefile bug pointed out by stain - Fixed Python 2.1 compatibility bug pointed out by stain - Migrated all regsub.[g]sub calls to re.sub - Fixed other Python 2.1 compatibility issues in Tree.py Changes since version 0.3: - Added the ability to select fonts/colors with $HOME/.forg/options - Fixed critical bug that was causing directory creation errors on windows systems. - Fixed fatal bug if the options file didn't exist. Whoops. :) - Fixed several bookmark editor bugs including disallowing cutting the root node, adding error dialogs for inactive conditions (like when the user tries to paste and no node is on the clipboard) - Disabled menu tearoffs. If anybody really wants them, I'll make them an option, but they seem to confuse windows users, and they're not exactly a super useful innovation. :) - Small GUI tweaks. Changes since version 0.2: Many bugfixes to the bookmark editor, including: - pasting folders into other folders - inserting folders as siblings of other folders - editing bookmarks and folder names in their places Display bugfixes, including a fix for some menu lockups. Program redesign - rather than having most of the logic of the program in TkGui, it has been separated out into a module allowing the FORG to be embedded in other applications. Improved caching and directory creation Addition of "purge cache" and "query cache" features. Changes since version 0.1: Added the "Purge Cache" functionality, which does the equivalent of a "rm -rf" on the users cache directory, which is usually $HOME/.forg/cache/. Added the "Cache Statistics" functionality to count files/directories and total filesize. Added support for the PIL (Python Imaging Library). If you have this software installed, it will be used to display images supported by the PIL inside the FORG's window when selecting images. This will only happen if you don't have an application bound to that filetype, and if the "Use PIL" option is checked. Added rudimentary bookmark editor, which isn't working yet, but gives you an idea of what it will be like. Added the Tree widget written by Charles E. "Gene" Cash . Used in the Bookmark Editor. forg-0.5.1.orig/Connection.py0100644000175000017500000002211707344462645016334 0ustar jgoerzenjgoerzen# Connection.py # $Id: Connection.py,v 1.18 2001/09/02 17:02:23 s2mdalle Exp $ # Written by David Allen # # Base class for socket connections. # # 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. ############################################################################# import socket import Options from string import * import utils import errno ConnectionException = "foo" class Connection: def __init__(self, *args): self._d = None self.bytes_transferred = {'sent' : 0, 'received' : 0 } return None def received(self, bytes): """Keep track of the number of bytes received on this socket object""" oldval = self.bytes_transferred['received'] self.bytes_transferred['received'] = oldval + bytes return self.bytes_transferred['received'] def sent(self, bytes): """Keep track of the number of bytes sent on this socket object""" self.bytes_transferred['sent'] = self.bytes_transferred['sent'] + bytes return self.bytes_transferred['sent'] def getBytesSent(self): return self.bytes_transferred['sent'] def getBytesRead(self): return self.bytes_transferred['received'] def readloop(self, sock, bytesToRead, msgBar=None): """Reads bytesToRead data off of sock or until EOF if bytesToRead < 0. Optionally uses msgBar to log information to the user.""" timesRead = 0 data = '' CHUNKSIZE = 1024 # Default read block size. # This may get overridden depending on how much data # we have to read. Optimally we want to read all of # the data that's going to come in 100 steps. if bytesToRead < 0: numKBtoRead = "" # Don't report total size since we don't know else: # Report the total size so the user has an idea of how long it's # going to take. val = float(bytesToRead) / float(1024) numKBtoRead = "of %0.2f kilobytes total size" % val if bytesToRead > (1024 * 100): # 100 Kb CHUNKSIZE = bytesToRead / 100 chunk = 'foobar' while len(chunk) > 0: self.checkStopped(msgBar) # Constantly make sure we should chunk = sock.recv(CHUNKSIZE) self.received(CHUNKSIZE) self.checkStopped(msgBar) # continue... timesRead = timesRead + 1 # print "Read %s: %s" % (CHUNKSIZE, timesRead * CHUNKSIZE) data = data + chunk if bytesToRead > 0 and len(data) >= bytesToRead: # print "BTR=%s, len(data)=%s, breaking" % (bytesToRead, # len(data)) # Disregard content length for broken gopher+ servers. # break pass if msgBar: # Report statistics on how far along we are... bytesRead = timesRead * CHUNKSIZE kbRead = (float(timesRead) * float(CHUNKSIZE)) / float(1024) if bytesToRead > 0: pct = (float(bytesRead) / float(bytesToRead))*float(100) # This happens sometimes when the server tells us to read # fewer bytes than there are in the file. In this case, # we need to only display 100% read even though it's # actually more. Becase reading 120% of a file doesn't # make sense. if pct >= float(100): pct = float(100) pctDone = ("%0.2f" % pct) + "%" else: pctDone = "" msgBar.message('state', "Read %d bytes (%0.2f Kb) %s %s" % (bytesRead, kbRead, numKBtoRead, pctDone)) # Break standards-compliance because of all those terrible gopher # servers out there. Return all of the data that we read, not just # the first bytesToRead characters of it. This will produce the user # expected behavior, but it disregards content lenghts in gopher+ if bytesToRead > 0: # return data[0:bytesToRead] return data else: return data def checkStopped(self, msgBar): """Issue a message to the user and jump out if greenlight isn't true.""" if not Options.program_options.GREEN_LIGHT: raise ConnectionException, "Connection stopped" def requestToData(self, resource, request, msgBar=None, grokLine=None): """Sends request to the host/port stored in resource, and returns any data returned by the server. This may throw ConnectionException. msgBar is optional. May throw ConnectionException if green_light ever becomes false. This is provided so that the user can immediately stop the connection if it exists.""" utils.msg(msgBar, "Creating socket...") self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) if not self.socket: raise GopherConnectionException, "Cannot create socket." self.checkStopped(msgBar) utils.msg(msgBar, "Looking up hostname...") try: # Try to get a cached copy of the IP address rather than # looking it up again which takes longer... ipaddr = Options.program_options.getIP(resource.getHost()) except KeyError: # OK, it wasn't in the cache. Figure out what it is, # and then put it in the cache. try: self.checkStopped(msgBar) ipaddr = socket.gethostbyname(resource.getHost()) except socket.error, err: host = resource.getHost() estr = "Cannot lookup\n%s:\n%s" % (host, err) raise ConnectionException, estr Options.program_options.setIP(resource.getHost(), ipaddr) # At this point, ipaddr holds the actual network address of the # host we're contacting. utils.msg(msgBar, "Connecting to %s:%s..." % (ipaddr, resource.getPort())) try: retval = self.socket.connect((ipaddr, int(resource.getPort()))) #if retval != 0: # errortype = errno.errorcode[retval] # raise socket.error, errortype except socket.error, err: newestr = "Cannot connect to\n%s:%s:\n%s" % (resource.getHost(), resource.getPort(), err) raise ConnectionException, newestr data = "" self.checkStopped(msgBar) self.socket.send(request) # Send full request - usually quite short self.checkStopped(msgBar) self.sent(len(request)) # We've sent this many bytes so far... if grokLine: # Read the first line...this is for Gopher+ retrievals line = "" # and usually tells us how many bytes to read later byte = "" while byte != "\n": self.checkStopped(msgBar) byte = self.socket.recv(1) if len(byte) <= 0: print "****************BROKE out of byte loop" break line = line + byte bytesread = len(line) line = strip(line) try: if line[0] == '+': bytecount = int(line[1:]) # Skip first char: "+-1" => "-1" resource.setLen(bytecount) # Read all of the data into the 'data' variable. data = self.readloop(self.socket, bytecount, msgBar) else: data = self.readloop(self.socket, -1, msgBar) except: print "*** Couldn't read bytecount: skipping." data = self.readloop(self.socket, -1, msgBar) else: data = self.readloop(self.socket, -1, msgBar) utils.msg(msgBar, "Closing socket.") self.socket.close() # FIXME: 'data' may be huge. Buffering? Write to cache file here # and return a cache file name? return data forg-0.5.1.orig/ContentFrame.py0100644000175000017500000000546007344462645016624 0ustar jgoerzenjgoerzen# ContentFrame.py # $Id: ContentFrame.py,v 1.6 2001/03/27 22:18:02 s2mdalle Exp $ # Written by David Allen # # This is the base class for anything that gets displayed by the program # in the main window to represent a response from the server. In other words, # things such as text boxes and directory listings from gopher servers should # extend this class. # # This class defines the behavior of anything that is presented to the user. # OK, so I'm a bit new to python. What I mean here is like a Java interface. # I know I can't force subclasses to 'implement' this function, but they # should, otherwise the functions here will spit out obscene messages to the # user. :) # # Note that creating the object is NOT ENOUGH. You must call pack_content # after creating it to actually add all the things that the widget will display # this is a workaround for some odd behavior in Pmw. For this reason you # should NOT rely on nothing being present in the widget if you don't call # pack_content. # # 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 Tkinter import * import Pmw class ContentFrame: useStatusLabels = None def __init__(self): pass def pack_content(self, *args): """Packs the content of the box into the frame. Note this does NOT pack this object into its parent, only its children into itself.""" print "ContentFrame.pack_content: Superclass failed to override." return None def find(self, term, caseSensitive=None, lastIdentifier=None): """Find some term within a frame. If caseSensitive is true, then the search will be caseSensitive. lastIdentifier is something that was previously returned by this function as the last match. If you want to find the next occurance of something, pass the last occurance in and it will search from then on.""" print "**********************************************************" print "***** ContentFrame.find(): Some subclass fucked up. *****" print "**********************************************************" return None forg-0.5.1.orig/GUIAskForm.py0100644000175000017500000000765007344462645016151 0ustar jgoerzenjgoerzen# GUIAskForm.py # Written by David Allen # Released under the terms of the GNU General Public License # # The Tk/widget incarnation of AskForm # # 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 Tkinter import * from gopher import * import Pmw import ContentFrame import GopherResource import GopherResponse import GopherConnection import AskForm import Question import GUIQuestion class GUIAskForm(ContentFrame.ContentFrame, Frame): verbose = None def __init__(self, parent_widget, parent_object, resp, resource, filename=None, menuAssocs={}): Frame.__init__(self, parent_widget) # Superclass constructor self.resource = resource self.response = resp self.parent = parent_object self.filename = filename self.question_widgets = [] self.questionBox = Pmw.ScrolledFrame(self, horizflex='fixed', vertflex ='fixed', hscrollmode='dynamic', vscrollmode='dynamic') return None def pack_content(self, *args): for x in range(0, self.response.questionCount()): q = self.response.nthQuestion(x) try: typename = questions_types["%s" % q.getType()] except KeyError: typename = "(ERROR NO TYPE)" print "PROCESSING Question %s %s %s" % (typename, q.getDefault(), q.getPromptString()) try: wid = GUIQuestion.GUIQuestion(self.questionBox.interior(), q) except Exception, errstr: print "Couldn't make wid: %s" % errstr continue wid.grid(row=x, col=0, sticky=W) self.question_widgets.append(wid) self.submit = Button(self, text="Submit", command=self.submit) self.questionBox.pack(side='top', expand=1, fill='both') self.submit.pack(side='bottom') return None def find(self, term, caseSensitive=None, lastIdentifier=None): self.parent.genericMessage("Sorry, AskForms are not searchable.\n" + "Or at least, not yet.") return None def submit(self, *args): """Submit the answers to the form questions to the server for processing, and load the results page.""" values = [] retstr = "" for widget in self.question_widgets: if widget.getType() == QUESTION_NOTE: continue values.append(widget.getResponse()) retstr = "%s%s" % (retstr, widget.getResponse()) print "Retstr is:\n%s" % retstr getMe = GopherResource.GopherResource() # Shouldn't cache this resource. getMe.setShouldCache(None) getMe.dup(self.resource) # Copy defaults l = len(retstr) # Tell the server how much data we're sending... retstr = "+%d\r\n%s" % (l, retstr) getMe.setDataBlock(retstr) return self.parent.goElsewhere(getMe) forg-0.5.1.orig/GUIDirectory.py0100644000175000017500000004307407344462645016553 0ustar jgoerzenjgoerzen# 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. forg-0.5.1.orig/GUIError.py0100644000175000017500000000336507344462645015677 0ustar jgoerzenjgoerzen# GUIError.py # $Id: GUIError.py,v 1.3 2001/02/22 03:01:03 s2mdalle Exp $ # # Displays errors similar to "Cannot Load Page" in the main window. # # 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 class GUIError(ContentFrame.ContentFrame, Frame): verbose = None def __init__(self, parent_widget, resource, error_message): Frame.__init__(self, parent_widget) ContentFrame.ContentFrame.__init__(self) self.parent = parent_widget Label(self, text="Unable to load").pack() Label(self, text=resource.toURL()).pack() Label(self, text=error_message).pack() def pack_content(self, *args): return None def find(self, term, caseSensitive=None, lastIdentifier=None): self.parent.genericError("Error: Error messages\n" + "are not searchable.") return None forg-0.5.1.orig/GUIFile.py0100644000175000017500000001154707344462645015466 0ustar jgoerzenjgoerzen# GUIFile.py # $Id: GUIFile.py,v 1.12 2001/07/05 17:16:53 s2mdalle Exp $ # Written by David Allen # # This is the class that describes how files behave when loaded into # the FORG. # # 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 Tkinter import * from gopher import * from string import * import Pmw import re import Options import ContentFrame import GopherResource import GopherResponse class GUIFile(ContentFrame.ContentFrame, Frame): verbose = None def __init__(self, parent_widget, parent_object, resp, resource, filename): Frame.__init__(self, parent_widget) # Superclass constructor self.resp = resp 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(expand=0, fill='x') Label(self, text=label2).pack(expand=0, fill='x') if resp.getTypeCode() != RESPONSE_FILE: Label(self, text="This file has been saved in:").pack() Label(self, text=filename).pack() else: self.textwid = Pmw.ScrolledText(self, hscrollmode='dynamic', vscrollmode='static') tw = self.textwid.component('text') tw.configure(background='#FFFFFF', foreground='#000000') self.textwid.component('text').bind('', parent_object.popupMenu) self.textwid.pack(expand=1, fill='both') return None def pack_content(self, *args): if Options.program_options.getOption('strip_carraige_returns'): # print "Stripping carriage returns..." data = replace(self.resp.getData(), "\r", "") else: data = self.resp.getData() if len(data) < 1024: self.textwid.settext(data) else: for index in range(0, len(data), 500): self.textwid.insert('end', data[index:index+500]) return None def destroy(self, *args): self.pack_forget() self.textwid.destroy() Frame.destroy(self) return None def find(self, term, caseSensitive=None, lastIdentifier=None): """Overrides the function of the same type from ContentFrame""" try: # This will raise an exception if it's a 'save' type layout # where the data isn't displayed to the user. tw = self.textwid.component('text') print "Component is ", tw except: # Don't mess with this. The user can read the entire label, all # big bad few lines of it. raise Exception, "This window is not searchable." if lastIdentifier is None: lastIdentifier = '0.0' # The variable caseSensitive is true if the search is case sensitive, # and false otherwise. But since tkinter wants to know whether or not # the search is case INsensitive, we flip the boolean value, and use # it for the 'nocase' keyword arg to the search method. csflipped = (not caseSensitive) pos = tw.search(pattern=term, forwards=1, nocase=csflipped, index=lastIdentifier, stopindex=END) if pos: # Find the real index of the position returned. found_index = tw.index(pos) else: found_index = None print "Found index is \"%s\"" % found_index if found_index: tw.yview(found_index) ending_index = tw.index("%s + %d chars" % (found_index, len(term))) # Set the selection to highlight the given word. tw.tag_add(SEL, found_index, ending_index) return tw.index("%s + 1 chars" % found_index) return None forg-0.5.1.orig/GUIQuestion.py0100644000175000017500000001304507344462645016411 0ustar jgoerzenjgoerzen# GUIQuestion.py # $Id: GUIQuestion.py,v 1.13 2001/07/02 22:50:39 s2mdalle Exp $ # Written by David Allen # Released under the terms of the GNU General Public License # # A GUI representation of a question. It should provide all methods needed # to get the response, set default values, etc. # # 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 Tkinter import * import Pmw import tkFileDialog from string import * from gopher import * import GopherResource import GopherResponse import GopherConnection import ContentFrame import Cache import Options import Question GUIQuestionException = "Sorry. Things just sometimes go wrong." class GUIQuestion(Frame): verbose = None def __init__(self, parent_widget, question): Frame.__init__(self, parent_widget) # Superclass constructor self.parent = parent_widget self.question = question self.type = self.question.getType() promptString = self.question.getPromptString() defaultValue = self.question.getDefault() Label(self, text=promptString).grid(row=0, col=0, sticky=W) if self.type == QUESTION_NOTE: # Prompt string is all we need for this one. return None if self.type == QUESTION_ASK: self.entry = Entry(self) if len(defaultValue) > 0: self.entry.insert('end', defaultValue) self.entry.grid(row=0, col=1, columnspan=4, sticky=W) return None if self.type == QUESTION_ASKF or self.type == QUESTION_CHOOSEF: self.entry = Entry(self) if len(defaultValue) > 0: self.entry.insert('end', defaultValue) self.entry.grid(row=0, col=1, columnspan=4, sticky=W) # Browse buttons for file selection. self.browse = Button(text="Browse", command=self.browse) self.browse.grid(row=0, col=5, sticky=W) return None if self.type == QUESTION_ASKP: self.entry = Entry(self, show="*") if len(defaultValue) > 0: self.entry.insert('end', defaultValue) self.entry.grid(row=0, col=1, columnspan=4, sticky=W) return None if self.type == QUESTION_ASKL: self.entry = Pmw.ScrolledText(self, hscrollmode='dynamic', text_width=80, text_height=6, vscrollmode='dynamic') self.entry.grid(row=1, col=0, columnspan=2, rowspan=2, sticky='N') return None if self.type == QUESTION_SELECT: self.entry = Pmw.RadioSelect(self, buttontype='checkbutton', command=self.changed) for opt in self.question.options: self.entry.add(opt) if defaultValue: print "Invoking defalut %s" % defaultValue self.entry.invoke(defaultValue) self.entry.grid(row=1, col=0, columnspan=4, rowspan=4, sticky='NSEW') print 'Returning SELECT GUIQuestion' return None if self.type == QUESTION_CHOOSE: self.entry = Pmw.RadioSelect(self, buttontype='radiobutton', command=self.changed) for opt in self.question.options: self.entry.add(opt) if defaultValue: print "Invoking defalut %s" % defaultValue self.entry.invoke(defaultValue) self.entry.grid(row=1, col=0, columnspan=4, rowspan=4, sticky='NSEW') print "Returning CHOOSE GUIQuestion" return None return None def browse(self, *args): dir = os.path.abspath(os.getcwd()) filename = tkFileDialog.asksaveasfilename(initialdir=dir) self.entry.delete(0, 'end') if filename: self.entry.insert('end', filename) return None def getType(self): return self.question.getType() def changed(self, *args): print "Selection changed: Current selection: ", args def getResponse(self): """Returns the current state of the widget, or what should be sent to the server.""" if self.entry.__class__ == Entry: return "%s\n" % self.entry.get() elif self.entry.__class__ == Pmw.ScrolledText: buf = self.entry.get() lines = count(buf, "\n") return "%d\n%s\n" % (lines, buf) elif self.entry.__class__ == Pmw.RadioSelect: list = self.entry.getcurselection() return "%s\n" % list[0] else: # Huh? What? Eh? WTF is going on? raise GUIQuestionException, "Cannot get content: Unknown type" return "" forg-0.5.1.orig/GUISaveFile.py0100644000175000017500000002136607344462645016305 0ustar jgoerzenjgoerzen# GUISaveFile.py # $Id: GUISaveFile.py,v 1.15 2001/07/05 17:16:53 s2mdalle Exp $ # Written by David Allen # # This class asks users graphically which filename they'd like to save certain # files into. This is generally used for downloaded files that don't have # associations bound to them, and that also can't be displayed, because # they're binary data or otherwise ugly to look at as text. :) (e.g. # *.zip, *.tar.gz, *.tgz, binhex, etc. # # Since this is a last resort, this module will try to import the Python # Imaging Library (PIL) to display images if it is present. Note that this # requires that there be no association for the image files, since if there # is, it won't even get as far as creating a GUISaveFile object to display. # # 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 Tkinter import * from gopher import * from string import * import os import tkFileDialog import Pmw import re import Options import ContentFrame import GopherResource import GopherResponse import Dialogs try: import PIL import Image import ImageTk except: print "Bummer dude! You don't have the PIL installed on your machine!" print "That means that the \"Use PIL\" option is going to be irrelevant" print "for you." class PILImage(Label): # This was borrowed and adapted from the PIL programming examples. def __init__(self, master, im): if im.mode == "1": # bitmap image self.image = ImageTk.BitmapImage(im, foreground="white") Label.__init__(self, master, image=self.image, bg="black", bd=0) else: # Photo image object would be better... self.image = ImageTk.PhotoImage(im) Label.__init__(self, master, image=self.image, bd=0) class GUISaveFile(ContentFrame.ContentFrame, Frame): verbose = None def __init__(self, parent_widget, parent_object, resp, resource, filename): Frame.__init__(self, parent_widget) # Superclass constructor self.r1 = None self.r2 = None self.filename = filename[:] self.parent = parent_object self.response = resp self.resource = resource # Don't even try to use the PIL unless the option is set # to allow it. usePIL = Options.program_options.getOption("use_PIL") if usePIL and self.canDisplay(): try: self.packImageContent() except Exception, errstr: self.packSaveContent() else: self.packSaveContent() print "Packed save content" return None def packImageContent(self, *args): self.createPopup() self.image = Image.open(self.filename) self.scrframe = Pmw.ScrolledFrame(self) imgwidget = PILImage(self.scrframe.interior(), self.image) imgwidget.pack() imgwidget.bind('', self.popupMenu) self.scrframe.pack(fill='both', expand=1) # Return and DON'T display the save file as box only if the # file had no problems. Otherwise an exception was raised. return None def revertToSaveDialog(self, *args): self.scrframe.pack_forget() # Unpack the scrolled frame self.scrframe = None # Lose the ref to the scrolled frame self.image = None # Lose the ref to the image object. self.packSaveContent() # Go back to the "Save File" content self.pack_content() return None def imageInfo(self, *args): try: if not self.image: return None except: return None info = "Bands: %s" % join(self.image.getbands(), ", ") size = self.image.size info = "%s\nWidth: %d pixels\nHeight: %d pixels" % (info, size[0], size[1]) info = "%s\nMode: %s" % (info, self.image.mode) for key in self.image.info.keys(): info = "%s\n%s = %s" % (info, key, self.image.info[key]) d = Dialogs.ErrorDialog(self, errstr=info, title='Image Information (PIL)') 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='Save', command=self.revertToSaveDialog) self.popup.add_command(label='Info', command=self.imageInfo) def popupMenu(self, event): """Display pop-up menu on right click on a message""" self.popup.tk_popup(event.x_root, event.y_root) def canDisplay(self): try: fn = Image.open except: return None s = self.filename ind = rfind(s, ".") if ind != -1 and ind != (len(s)-1): fileExtension = lower(s[ind:]) return 1 def find(self, term, caseSensitive=None, lastIdentifier=None): self.parent.genericError("Error: Save Dialogs\n" + "are not searchable.") return None def pack_content(self, *args): return None def packSaveContent(self, *args): # Explicit copy - damn python and its ability to trip me up with all # that "everything's a reference" stuff. :) default_filename = self.filename[:] for char in ['/', ':', ' ', '\\']: strtofind = "%" + "%d;" % ord(char) default_filename = re.sub(strtofind, char, default_filename) for separator in ['/', ':', '\\']: ind = rfind(default_filename, separator) if ind != -1: default_filename = default_filename[ind+len(separator):] break 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(expand=0, fill='x') Label(self, text=label2).pack(expand=0, fill='x') Label(self, text=" ").pack() # Empty line. Label(self, text="Please enter a filename to save this file as:").pack() cframe = Frame(self) cframe.pack(expand=1, fill='both') Label(cframe, text="Filename:").pack(side='left') self.filenameEntry = Entry(cframe) self.filenameEntry.insert('end', default_filename) self.filenameEntry.pack(side='left', expand=1, fill='x') self.filenameEntry.bind("", self.save) Button(cframe, text="Browse", command=self.browse).pack(side='right') self.saveButton = Button(cframe, text="Save", command=self.save) self.saveButton.pack(side='right') return None def browse(self, *args): dir = os.path.abspath(os.getcwd()) filename = tkFileDialog.asksaveasfilename(initialdir=dir) if filename: self.filenameEntry.delete(0, 'end') self.filenameEntry.insert('end', filename) return None def save(self, *args): filename = self.filenameEntry.get() try: fp = open(filename, "w") fp.write(self.response.getData()) fp.flush() fp.close() except IOError, errstr: self.parent.genericError("Couldn't save file\n%s:\n%s" % (filename, errstr)) return None if self.r1: self.r1.destroy() if self.r2: self.r2.destroy() self.r1 = Label(self, text="File successfully saved into").pack() self.r2 = Label(self, text=filename).pack() return None forg-0.5.1.orig/GUISearch.py0100644000175000017500000000713607344462645016013 0ustar jgoerzenjgoerzen# GUISearch.py # $Id: GUISearch.py,v 1.6 2001/04/15 19:27:00 s2mdalle Exp $ # Written by David Allen # # Released under the terms of the GNU General Public License # # This is the graphical component used for getting information from the user # about search terms. (And then sending them on their merry way) # # 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 Tkinter import * from gopher import * import Pmw import ContentFrame import GopherResource import GopherResponse import GopherConnection class GUISearch(ContentFrame.ContentFrame, Frame): verbose = None def __init__(self, parent_widget, parent_object, resp, resource, filename=None, menuAssocs={}): Frame.__init__(self, parent_widget) # Superclass constructor self.resource = resource self.response = resp self.parent = parent_object self.filename = filename labels = ["Please enter a space-separated list of search terms."] last = 0 for x in range(0, len(labels)): last = x label = labels[x] Label(self, text=label, foreground='#000000').grid(row=x, col=0, columnspan=2) self.entryArea = Frame(self) self.entryArea.grid(row=(x+1), col=0, columnspan=5, sticky='EW') self.entryBox = Entry(self.entryArea, text='') self.entryBox.pack(side='left', expand=1, fill='x') self.entryBox.bind("", self.submit) self.GO = Button(self.entryArea, text='Submit', command=self.submit) self.GO.pack(side='right') self.bottom_label = None return None def pack_content(self, *args): return None def find(self, term, caseSensitive=None, lastIdentifier=None): self.parent.genericError("Error: Search boxes\n" + "are not searchable.") return None def submit(self, *args): terms = self.entryBox.get() print "Terms are \"%s\"" % terms if self.bottom_label: self.bottom_label.destroy() self.bottom_label = Label(self, "Searching for \"%s\"" % terms) self.bottom_label.grid(row=10, col=0, columnspan=2, sticky=W) # Copy the data from the current resource. res = GopherResource.GopherResource() res.setHost(self.resource.getHost()) res.setPort(self.resource.getPort()) res.setName(self.resource.getName()) # This is a nasty way of faking the appropriate protocol message, # but oh well... res.setLocator("%s\t%s" % (self.resource.getLocator(), terms)) res.setInfo(None) res.setLen(-2) res.setTypeCode(RESPONSE_DIR) # Response *will* be a directory. self.parent.goElsewhere(res) return None forg-0.5.1.orig/GopherConnection.py0100644000175000017500000001422207344462645017477 0ustar jgoerzenjgoerzen# GopherConnection.py # $Id: GopherConnection.py,v 1.16 2001/07/14 22:51:57 s2mdalle Exp $ # Written by David Allen # Released under the terms of the GNU General Public License # # This object handles connections and sending requests to gopher servers. # # 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. ####################################################################### import socket import re import utils from string import * import GopherResponse import Connection import GopherObject import GopherResource import ResourceInformation import AskForm from gopher import * GopherConnectionException = "Error: " class GopherConnection(Connection.Connection): SNARFSIZE = 1024 verbose = None def __init__(self, server="gopher://gopher.floodgap.com", port=70): Connection.Connection.__init__(self) self.server = re.sub("gopher://", "", server, 1) self.port = port self.forgetResponse() return None def forgetResponse(self): """Remove the response field of this object. This is periodically necessary, since it can get quite large.""" self.response = None return self.response def stripTail(self, data): ind = rfind(data, "\r\n.\r\n") if ind != -1: if self.verbose: print "Stripping protocol footer at index %d" % int(ind) return data[0:ind] if self.verbose: print "No protocol footer found." return data # Get extended information about a resource. def getInfo(self, resource, msgBar=None): try: req = "%s\t!\r\n" % resource.getLocator() data = self.requestToData(resource, req, msgBar) except Connection.ConnectionException, errstr: raise GopherConnectionException, errstr if self.verbose: print "Got %d bytes from INFO conn:\n%s" % (len(data), data) # The server sends back a length of -2 when it doesn't know how long # the document is, and when the document may contain the \r\n.\r\n # pattern. So if it might, do not strip it out. Otherwise do. This # will probably keep out a very subtle bug of having files downloaded # be truncated in really weird places. (Where the \r\n.\r\n would have # been) if resource.getLen() != -2: if self.verbose: print "Stripping protocol footer." data = self.stripTail(data) try: info = ResourceInformation.ResourceInformation(data) except Exception, estr: print "*********************************************************" print "***GopherConnection: ResourceInformation Error: %s" % estr print "*********************************************************" raise GopherConnectionException, estr return info def getResource(self, resource, msgBar=None): self.forgetResponse() self.host = re.sub("gopher://", "", resource.getHost(), 1) self.port = resource.getPort() self.lastType = resource.getType() try: if resource.getDataBlock(): request = resource.getLocator() + "\t+\t1\r\n" request = request + resource.getDataBlock() self.response = self.requestToData(resource, request, msgBar, 1) elif resource.isGopherPlusResource() and resource.isAskType(): info = resource.getInfo(shouldFetch=1) af = AskForm.AskForm(info.getBlock("ASK")) # Copy host/port/locator information into af af.dup(resource) return af elif resource.isGopherPlusResource(): request = resource.getLocator() + "\t+\r\n" self.response = self.requestToData(resource, request, msgBar, 1) else: request = resource.getLocator() + "\r\n" self.response = self.requestToData(resource, request, msgBar, None) except Connection.ConnectionException, estr: error_resp = GopherResponse.GopherResponse() errstr = "Cannot fetch\n%s:\n%s" % (resource.toURL(), estr) error_resp.setError(errstr) return error_resp utils.msg(msgBar, "Examining response...") resp = GopherResponse.GopherResponse() resp.setType(resource.getTypeCode()) if resource.getLen() != -2: self.response = self.stripTail(self.response) try: # The parser picks up directory entries and sets the internal # data of the object as needed. resp.parseResponse(self.response) # if we get this far, then it's a directory entry, so set the # data to nothing. resp.setData(None) except Exception, erstr: # print "Caught exception while parsing response: \"%s\"" % erstr if self.verbose: print "OK, it's data." resp.setData(self.response) return resp forg-0.5.1.orig/GopherObject.py0100644000175000017500000002017107344462645016606 0ustar jgoerzenjgoerzen# GopherObject.py # $Id: GopherObject.py,v 1.17 2001/07/09 22:31:32 s2mdalle Exp $ # Written by David Allen # Released under the terms of the GNU General Public License # # Base class for GopherResource, GopherResponse # # 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. ############################################################################# import re import os import utils from string import * from gopher import * GopherObjectException = "Whoops...something went wrong!" class GopherObject: def __init__(self, typecode = None, host = None, port = None, locator = None, name = None, len = -2): self.__class = "GopherObject" self._shouldCache = "YES" self.setTypeCode(typecode) self.setHost(host) self.setPort(port) self.setLocator(locator) self.setName(name) self.setLen(len) self.setDataBlock("") return None def dup(self, res): """Returns a copy of this object.""" self.setLen(res.getLen()) self.setTypeCode(res.getTypeCode()) self.setHost(res.getHost()) self.setLocator(res.getLocator()) self.setName(res.getName()) self.setPort(res.getPort()) return self # Accessors, mutators def shouldCache(self): return self._shouldCache def setShouldCache(self, boolval): self._shouldCache = boolval return self.getShouldCache() def getShouldCache(self): return self._shouldCache def getLen(self): return self.len def setLen(self, newlen): self.len = newlen return self.len def getTypeCode(self): return self.type[0] def setDataBlock(self, block): self.datablock = block return self.datablock def getDataBlock(self): return self.datablock def setType(self, newtype): return self.setTypeCode(newtype) def setTypeCode(self, newtype): self.type = newtype return self.type def getType(self): """Return a string representing the type code of the response. See the gopher module for more information.""" try: return responses[self.type] except KeyError: return "-Unknown-" def getHost(self): return self.host def setHost(self, newhost): self.host = newhost return self.host def getPort(self): return self.port def setPort(self, newport): self.port = newport return self.port def getLocator(self): return self.locator def setLocator(self, newlocator): self.locator = newlocator return self.locator def getName(self): if strip(self.name) == '/': self.setName("%s root" % self.getHost()) elif self.name == '' or self.name is None: loc = strip(self.getLocator()) if loc == '' or loc == '/': self.setName("%s root" % self.getHost()) else: self.setName(" ") # self.setName("%s %s" % (self.getHost(), self.getLocator())) return self.name def setName(self, newname): self.name = newname return self.name # Methods def toURL(self): """Return the URL form of this GopherResource""" return "gopher://%s:%s/%s%s" % (self.getHost(), self.getPort(), self.getTypeCode(), re.sub("\t", "%9;", self.getLocator())) def toProtocolString(self): """Returns the protocol string, i.e. how it would have been served by the server, in a string.""" return "%s%s\t%s\t%s\t%s\r\n" % (self.getTypeCode(), self.getName(), self.getLocator(), self.getHost(), self.getPort()) def toXML(self): """Returns a small XML tree corresponding to this object. The root element is the name of the object. Override me in subclasses.""" tags = [["type", self.type], ["locator", self.locator], ["host", self.host], ["port", self.port], ["name", self.name]] str = "" for tag in tags: str = str + "<%s>%s" % (tag[0], tag[1], tag[0]) str = str + "" return str def __str__(self): return self.__class # return self.toString() def toString(self): """Returns a string form of this object. Mostly only good for debugging.""" return ("Type: %s\nLocator: %s\nHost: %s\nPort: %s\nName: %s\n" % (self.getTypeCode(), self.getLocator(), self.getHost(), self.getPort(), self.getName())) def filenameToURL(self, filename): """Unencodes filenames returned by toFilename() into URLs""" try: return utils.character_replace(filename, os.sep, "/") except: # os.sep is '/' - character_replace doesn't allow replacing a # character with itself. we're running Eunuchs. return filename def toCacheFilename(self): filename = self.toFilename() lastchr = filename[len(filename)-1] if lastchr == os.sep or lastchr == '/': # Pray for no name clashes... :) # We have to call it something inside the leaf directory, because # if you don't, you get into the problem of caching responses from # directories as files, and not having a directory to put the items # in the directory in. And you can't call the file "" on any # filesystem. :) filename = "%sgopherdir.idx" % filename elif self.getTypeCode() == RESPONSE_DIR: filename = "%s%sgopherdir.idx" % (filename, os.sep) port = self.getPort() str_to_find = ":%d" % int(port) # Cut out the port portion of the filename. This is because some # OS's throw up with ':' in filenames. Bummer, but this will make it # hard to translate a filename/path -> URL ind = find(filename, str_to_find) if ind != -1: filename = "%s%s" % (filename[0:ind], filename[ind+len(str_to_find):]) if os.sep == '\\': # No-name mac wannabe OS's... :) for char in ['/', ':', ';', '%', '*', '|']: # This isn't necessarily a good idea, but it's somewhat # necessary for windows boxen. filename = utils.character_replace(filename, char, ' ') return filename def toFilename(self): """Returns the name of a unique file containing the elements of this object. This file is not guaranteed to not exist, but it probably doesn't. :) Get rid of all of the slashes, since they are Frowned Upon (TM) by most filesystems.""" replaceables = ['\t', '\n', '\\', '/'] data = self.toURL() if find(lstrip(lower(data)), "gopher://") == 0: # Chomp the "gopher://" part data = data[len("gopher://"):] for r in replaceables: try: data = utils.character_replace(data, r, os.sep) except: pass return data forg-0.5.1.orig/GopherResource.py0100644000175000017500000001522607344462645017174 0ustar jgoerzenjgoerzen# GopherResource.py # $Id: GopherResource.py,v 1.13 2001/07/11 22:43:09 s2mdalle Exp $ # Written by David Allen # # This class defines the information needed for a gopher resource. That # usually contains all the needed information about one instance of a file, # directory, or other "thingy" on a gopher server. This class extends # GopherObject which gives it most of its accessors/mutators. # # 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. ############################################################################# import re from string import * from urlparse import * from gopher import * import GopherConnection import GopherObject # import Options class GopherResource(GopherObject.GopherObject): verbose = None debugging = None # Set to true for messages at the prompt, etc. def __init__(self, type = RESPONSE_DIR, host = "gopher.floodgap.com", port = 70, locator = "/", stringName = "", auxFields = []): GopherObject.GopherObject.__init__(self, type, host, port, locator, stringName) self.__class = "GopherResource" if self.debugging: print "NEW GOPHER RESOURCE: " + self.toString() self.info = None self.setAuxFields(auxFields) return None def setInfo(self, newinfo): self.info = newinfo return self.info def getInfo(self, shouldFetch=None): """Returns the ResourceInformation block associated with this Resource. If shouldFetch is true, the resource block will fetch information about itself if none is present.""" if not self.info and shouldFetch: try: self.setInfo(conn.getInfo(self)) except Exception, errstr: print "**** GopherResource couldn't get info about itself:" print errstr # This is bad. self.setInfo(None) return self.info def setAuxFields(self, fields): self.auxFields = fields if len(self.auxFields) > 0 and self.auxFields[0] == '?': # We need to fetch information about this one, since it's # going to contain ASK blocks. conn = GopherConnection.GopherConnection() try: self.setInfo(conn.getInfo(self)) except Exception, errstr: print "**** GopherResource couldn't get info about itself:" print errstr # This is bad. self.setInfo(None) return self.auxFields def getAuxFields(self): return self.auxFields def isAskType(self): if not self.isGopherPlusResource(): return None if len(self.auxFields) > 0 and strip(self.auxFields[0]) == '?': return 1 else: return None return None def isGopherPlusResource(self): if len(self.auxFields) > 0: return 1 else: return None def toProtocolString(self): """Overrides the GopherObject method of the same name to provide support for printing out the auxFields in this object.""" return "%s%s\t%s\t%s\t%s\t%s\r\n" % (self.getTypeCode(), self.getName(), self.getLocator(), self.getHost(), self.getPort(), join(self.getAuxFields(), "\t")) def toXML(self): """Returns a small XML tree corresponding to this object. The root element is the name of the object. Override me in subclasses.""" tags = [["type", self.getType()], ["locator", self.getLocator()], ["host", self.getHost()], ["port", self.getPort()], ["name", self.getName()]] str = "" for tag in tags: str = str + "<%s>%s" % (tag[0], tag[1], tag[0]) str = str + "" return str def toURL(self): """Return the URL form of this GopherResource""" return "gopher://%s:%s/%s%s" % (self.getHost(), self.getPort(), self.getTypeCode(), self.getLocator()) def setURL(self, URL): """Take a URL string, and convert it into a GopherResource object. This destructively modifies this object and returns a copy of itself FROM RFC 1738: Gopher URLs look like this: gopher://host:port/TypeCodeLocator where TypeCode is a one-character code corresponding to some entry in gopher.py (hopefully) and Locator is the locator string for the resource. """ thingys = urlparse(URL) type = thingys[0] hostport = thingys[1] resource = thingys[2] sublist = split(hostport, ":", 2) host = sublist[0] try: port = sublist[1] port = int(port) except IndexError: port = 70 except ValueError: port = 70 self.setHost(host) self.setPort(port) # Strip the leading slash from the locator. if resource != '' and resource[0] == '/': resource = resource[1:] if len(resource) >= 2: newtypecode = resource[0] locator = resource[1:] else: newtypecode = RESPONSE_DIR locator = "/" self.setLocator(locator) self.setName(self.getLocator()) self.setTypeCode(newtypecode) return self # Return a copy of me # End GopherResource def URLtoResource(URL): """Non-class method mimicing GopherResource.setURL""" res = GopherResource() return res.setURL(URL) forg-0.5.1.orig/GopherResponse.py0100644000175000017500000002244407344462645017203 0ustar jgoerzenjgoerzen# GopherResponse.py # $Id: GopherResponse.py,v 1.15 2001/07/09 22:32:14 s2mdalle Exp $ # Contains GopherResource and GopherResponse objects # Written by David Allen # Released under the terms of the GNU General Public License # # This object holds the data corresponding to how a gopher server responded # to a given request. It holds one of two things in general, either a pile # of data corresponding to text, a gif file, whatever, or it holds a list of # GopherResource objects. (This is when it's a directory entry that's being # stored. # # 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. ############################################################################## import re from string import * from urlparse import * from gopher import * import Connection import GopherConnection import GopherObject import GopherResource import ResourceInformation import Options GopherResponseException = "Gopher response error" class GopherResponse(GopherObject.GopherObject): verbose = None def __init__(self, type=None, host=None, port=None, loc=None, name=None): GopherObject.GopherObject.__init__(self, type, host, port, loc, name) self.__class = "GopherResponse" self.data = None self.responses = [] return None def toProtocolString(self): if self.getData() == None: def protString(item): return item.toProtocolString() return join(map(protString, self.getResponses()), "") else: return self.getData() return None def writeToFile(self, filename): """Writes the contents of this response to a disk file at filename this may raise IOError which the caller should deal with""" fp = open(filename, "w") if self.getData() == None: for resp in self.getResponses(): fp.write(resp.toProtocolString()) else: fp.write(self.getData()) fp.flush() fp.close() return filename def getResponses(self): """Return a list of responses. This is only really good when the response was a directory and set of entries.""" if self.getData() != None: raise GopherResponseException, "Get data instead." return self.responses def getData(self): """Return the data associated with the response. This is usually all of the data off of the socket except the trailing closer.""" return self.data def getDataLength(self): return len(self.data) def getDataChunk(self, startIndex, endIndex=-1): """This method provides a way to get chunks of data at a time, rather than snarfing the entire ball.""" if endIndex == -1: endIndex = len(self.data) return self.data[startindex, endIndex] def getError(self): """If there was an error message, return it.""" try: return self.error except: return None def setError(self, errorstr): """Modify the error message within this response.""" self.error = errorstr return self.getError() def setData(self, data): """Modify the data within the response. You probably don't want to do this.""" self.data = data return None def looksLikeDir(self, data): """Takes a chunk of data and returns true if it looks like directory data, and false otherwise. This is tricky, and is of course not 100%. Basically, we look at each line, see if the first bit in the line is a legal type, check to see that the line has at least 3 tabs in it. If all of the first 20 lines of the data follow that rule, then it's good enough to be used as directory data. If not, it gets chucked. Notice that if this really is a directory but it's using file types we've never heard of (see gopher.py) then it will still get thrown out. Bummer. This should only be called anyway if the type indictator is incorrect, so cope. :)""" def linefn(l): return replace(l, "\r", "") # Some very strange non-standards compliant servers send \r on some # lines and not on others. So split by newline and remove all # carriage returns as they occur. lines = map(linefn, split(data, "\n", 10)) for line in lines: d = strip(line) if not d or d == '' or d == '.': continue if count(line, "\t") < 2: return None # Not enough tabs. Bummer. isResponse = None byte = line[0] try: resp = responses[byte] isRespose = 1 except: pass if isResponse: continue try: resp = errors[byte] except: return None if len(lines) > 0: return 1 # Matched all tests for max 20 lines. Looks OK. else: return 0 # Empty data isn't a directory. def parseResponse(self, data): """Takes a lump of data, and tries to parse it as if it was a directory response. It will set the responses array to the proper thing if the result was good, so that you can use self.getRepsonses() to access them. Otherwise it raises GopherResponseException""" self.responses = [] if self.type == RESPONSE_DIR: pass # Keep going elif self.looksLikeDir(data): self.type = RESPONSE_DIR else: raise GopherException, "This isn't a directory." def stripCR(dat): return replace(dat, "\r", "") # This is done because \r may or may not be present, so we can't # split by \r\n because it fails for some misbehaved gopher servers. self.lines = map(stripCR, split(data, "\n")) for line in self.lines: if len(line) <= 1: continue # Type of the resource. See gopher.py for the possibilities. stype = "%s" % line[0] line = line[1:] # Gopher protocol uses tab delimited fields... linedata = split(line, "\t") name = "Unknown" # Silly defaults locator = "Unknown" host = "Unknown" port = 70 try: name = linedata[0] # Assign the right things in the except IndexError: pass # right places (maybe) try: # Catch those errors coming from locator = linedata[1] # the line not being split into except IndexError: pass # enough tokens. Realistically, try: # if those errors happen, host = linedata[2] # something is really screwed up except IndexError: pass # and there's not much we can do try: # anyway. port = linedata[3] except IndexError: pass try: remainder = linedata[4:] # Extra Gopher+ fields. except: remainder = [] # UMN gopherd errors do this sometimes. It's quite annoying. # they list the response type as 'directory' and then put the host # as 'error.host' to flag errors if host == 'error.host' and stype != RESPONSE_BLURB: stype = RESPONSE_ERR newresource = GopherResource.GopherResource(stype, host, port, locator, name, remainder) # Are the options set to allow us to get info? # THIS SHOULD BE USED WITH CAUTION since it can slow things down # more than you might think. if Options.program_options.getOption('grab_resource_info'): if len(remainder) >= 1 and remainder[0] == '+': try: conn = GopherConnection.GopherConnection() info = conn.getInfo(newresource) newresource.setInfo(info) except GopherConnectionException, estr: print "***(GCE) can't get info: %s" % estr except Exception, estr: print "***(unknown) Couldn't %s %s" % (Exception,estr) self.responses.append(newresource) return None # End GopherResponse forg-0.5.1.orig/INSTALL0100644000175000017500000000123707344462645014714 0ustar jgoerzenjgoerzenTO INSTALL THE FORG: There isn't really any installation procedure. If you wish, you can copy all of the program files to a different directory. But other than that, there is no installation, simply run the "forg.py" script. Typing "make install" at the prompt will create a ".forg" directory in your $HOME directory, and copy the default bookmarks into their proper location, but it won't change the location of any of the files in this directory. No matter what you do, when you run the program, directories underneath your $HOME will be created to save the program's information, settings, bookmarks, and to hold cached data. That directory will be $HOME/.forg forg-0.5.1.orig/INSTALL_FORG.bat0100644000175000017500000000077207344462645016301 0ustar jgoerzenjgoerzenrem This is a batch file written to install the FORG on windows systems. rem But since I haven't used windows or DOS in quite a while, it may be rem completely broken. Please let me know if it is. :) rem David Allen rem rem Don't try to run this if you're not on a machine that has some rem form of DOS installed. :) rem Create the options directory: md c:\forg-data copy default-bookmarks.xml c:\forg-data\bookmarks rem Install the program in C:\FORG md c:\FORG copy *.py c:\FORG forg-0.5.1.orig/List.py0100644000175000017500000002132107344462645015144 0ustar jgoerzenjgoerzen# List.py # $Id: List.py,v 1.8 2001/08/12 20:40:14 s2mdalle Exp $ # Written by David Allen # # This is a data structure similar to a doubly linked list, but with a few # exceptions - it has to keep track of all nodes that have EVER been in the # list, to cache old nodes for jumping around. It also has to know that when # you insert a completely new item, it removes everything after the current # slot in the list. This is because when you jump to a new document like in # a web browser, you can't then hit the 'forward' button. # # The forward and back buttons in the program will correspond to methods in # this object. # # Only put ListNode objects into this. # # 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 ListNode import * ListException = "List error" class List: def __init__(self): self.front = ListNode() self.back = ListNode() self.front.setNext(self.back) self.front.setPrev(None) self.back.setPrev(self.front) self.back.setNext(None) self.current = self.front return None def goToFront(self): """Sets the current node to the first node in the list.""" self.current = self.front.next return None def goToBack(self): """Sets the current node to the last node in the list.""" self.current = self.back.prev return None def atEnd(self): """Predicate: Returns true if the current item is the last item in the list.""" return self.current == self.back def atFront(self): """Predicate: Returns true if the current item is the first item in the list.""" return self.current == self.front def itemsRemaining(self): """Predicate: Returns true if the current item is not the last item""" return not self.atEnd() def isEmpty(self): """Predicate: Returns true if the list contains > 0 items.""" if self.front.getNext() == self.back: return 1 else: return None def traverse(self, function): """Takes a function argument, returns a list which contains the result of applying the function to each item in the list successivly. Sort of like map() for this list particularly.""" n = self.front.getNext() listresults = [] while n != self.back: listresults.append(function(n)) n = n.getNext() return listresults def insertAfter(self, newnode, afterWhat): """Inserts newnode after afterWhat. afterWhat may actually be either a ListNode in the list, or it may be the data inside a particular ListNode. But it must be a reference to the *same* data, (or node) not a reference to equivalent data (or node).""" n = self.front.getNext() if newnode.__class__ != ListNode: raise ListException, "newnode argument must be a ListNode" while n != self.back.getPrev(): if afterWhat == n or afterWhat == n.getData(): nn = n.getNext() newnode.setPrev(n) newnode.setNext(nn) afterWhat.setNext(newnode) nn.setPrev(newnode) return newnode n = n.getNext() raise ListException, "cannot insert after nonexistent node." def removeReference(self, ref): """Removes ref from the list. Note this must be a reference to something in the list, not just something that has the same data.""" n = self.front.getNext() while n != self.back: # We're going to be very forgiving and let the user pass us a # reference to either the data contained in the ListNode object, # or a reference to the ListNode object itself. if n == ref or n.getData() == ref: np = n.getPrev() nn = n.getNext() n.setNext(None) # Kill the links on the node we delete... n.setPrev(None) np.setNext(nn) # Update the links on the surrounding nn.setPrev(np) # nodes... return ref # Get out...we only remove the 1st one. n = n.getNext() # Next item in the list. raise ListException, "Reference not found in list" def countNodes(self, f=None): """Returns the number of nodes in the list.""" if f is None: f = self.front nodes = 0 n = f.getNext() while n != self.back: nodes = nodes + 1 n = n.getNext() return nodes def prepend(self, node): """Inserts the given node at the front of the list.""" self.current = self.front return self.insert(node, truncate=0) def postpend(self, node): """Inserts the given node at the very back of the list.""" self.current = self.back.getPrev() return self.insert(node, 0) def insert(self, node, truncate=1): """Inserts node as the next item in the list. If truncate is true, then all subsequent elements are dropped, and the new node becomes the last node in the list.""" if truncate: node.setPrev(self.current) node.setNext(self.back) self.current.setNext(node) self.current = node return self.current else: oldnext = self.current.getNext() node.setPrev(self.current) node.setNext(oldnext) self.current.setNext(node) oldnext.setPrev(node) self.current = node return self.current def getCurrent(self): """Returns the current node.""" return self.current def removeCurrent(self): """Removes the current node. The current node then becomes the next node in the list.""" if not self.current: raise ListException, "Error: Cannot delete NONE" if self.current == self.front: raise ListException, "Cannot delete FRONT" if self.current == self.back: raise ListException, "Cannot delete BACK" one_before_this_one = self.current.getPrev() one_after_this_one = self.current.getNext() one_before_this_one.setNext(one_after_this_one) one_after_this_one.setPrev(one_before_this_one) self.current.setPrev(None) self.current.setNext(None) self.current.setData(None) self.current = one_after_this_one return self.current def getFirst(self): """Returns the first item in the list. Does not change the current node""" first = self.front.next if first == self.back: raise ListException, "The list is empty" return first def getLast(self): """Returns the last item in the list. Does not change the current node""" last = self.back.prev if last == self.front: raise ListException, "The list is empty" return last def getNext(self): """Returns the next node in the list, and advances the current node.""" next = self.current.getNext() if next == self.back or (next == None and next == self.back): raise ListException, "Already at the end of the list" elif next == None: raise ListException, "getNext(): Null next field" self.current = next return self.current def getPrev(self): """Returns the previous node in the list, which then becomes the current node.""" prev = self.current.getPrev() if prev == self.front or (prev == None and prev == self.front): raise ListException, "Already at the beginning of the list" elif prev == None: raise ListException, "getPrev(): Null prev field." self.current = self.current.getPrev() return self.current # EOF forg-0.5.1.orig/ListNode.py0100644000175000017500000000366507344462645015765 0ustar jgoerzenjgoerzen# ListNode.py # $Id: ListNode.py,v 1.6 2001/07/11 22:43:09 s2mdalle Exp $ # Nodes to be placed in List objects. # Subclass freely! Life is short! # # Written by David Allen # # 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. ############################################################################# class ListNode: def __init__(self, data=None, next=None, prev=None): self.next = next self.prev = prev self.data = data return None def __str__(self): return self.data.__str__ def __repr__(self): return self.data.__repr__ def getNext(self): """Return the next item in the list""" return self.next def getPrev(self): """Return the previous item in the list""" return self.prev def getData(self): """Return the data inside this object Node""" return self.data def setNext(self, newnext): """Set the next item in the list""" self.next = newnext return self.next def setPrev(self, newprev): """Set the previous item in the list""" self.prev = newprev return self.prev def setData(self, data): """Set the data inside the object.""" self.data = data return self.data forg-0.5.1.orig/Makefile0100644000175000017500000000153107344462645015320 0ustar jgoerzenjgoerzen# Rudimentary Makefile for the FORG # # David Allen # # Running ./forg.py hostname is sufficient to run the program, so don't # use make at all unless you know what this does. :) # # $Id: Makefile,v 1.12 2001/07/11 22:45:51 s2mdalle Exp $ ############################################################################### srcdir = `pwd` PYTHON = /usr/bin/python RM = /bin/rm -f CONFDIR = "$(HOME)/.forg" all: $(PYTHON) forg.py gopher.quux.org clean-cache: $(RM) -r $(CONFDIR)/cache/* clean: @echo Yeeeeeeeeeeeeeehaw\!\!\!\!\! $(RM) *.pyc *~ dist: cd .. && tar cvf forg-latest.tar $(srcdir) --exclude CVS && \ gzip -9 forg-latest.tar install: mkdir -v -p $(CONFDIR) cp -i $(srcdir)/default_bookmarks.xml $(CONFDIR)/bookmarks cp -i $(srcdir)/default_options $(CONFDIR)/options restore: clean clean-cache @echo "Done" forg-0.5.1.orig/Options.py0100644000175000017500000002112407344462645015665 0ustar jgoerzenjgoerzen# Options.py # $Id: Options.py,v 1.18 2001/07/14 23:26:27 s2mdalle Exp $ # Written by David Allen # # Eventually this will hold all program options, and maybe be incorporated # into some sort of options editor. (Hopefully) # # For now, it holds information about what the program should and shouldn't # do. Variable names should be pretty self explanatory. # # 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. ############################################################################# import os from string import * import Cache import Associations class Options: def __init__(self, *args): # Default values for some important options... self.ip_cache = {} self.cache = Cache.Cache() self.associations = Associations.Associations() self.setDefaultOpts() self.greenLight() return None # Accessors/Mutators def getCache(self): return self.cache def setCache(self, newcache): self.cache = newcache return self.getCache() def getAssociations(self): return self.associations def setAssociations(self, newassoc): self.associations = newassoc return self.getAssociations() def getIP(self, hostname): """Return the cached IP of hostname. May throw KeyError""" return self.ip_cache[hostname] def setIP(self, hostname, IP): self.ip_cache[hostname] = IP return self.getIP(hostname) def save(self, alternate_filename=None): """Saves all options to the prefs directory/forgrc unless an alternate filename is specified. Throws all IOErrors out""" if not alternate_filename: prefs = self.getOption('prefs_directory') filename = "%s%s%s" % (prefs, os.sep, "forgrc") else: filename = alternate_filename fp = open(filename, "w") fp.write(self.toString()) fp.flush() fp.close() return 1 def toggle(self, key): """Flip the value of the option specified by key. If it was true, it becomes false, and if it was false, it becomes true.""" try: val = self.opts[key] if val: print "Toggle(%s): FALSE" % key self.opts[key] = None else: print "Toggle(%s): TRUE" % key self.opts[key] = 1 except: print "Toggle(%s): TRUE" % key self.opts[key] = 1 return self.opts[key] def greenLight(self): """Set the green light. This is used for thread synchronization""" self.GREEN_LIGHT = 1 return self.GREEN_LIGHT def redLight(self): """Turn off the green light. For thread synchronization.""" self.GREEN_LIGHT = None return self.GREEN_LIGHT def exists(self, filename): """Returns true if filename exists, false otherwise.""" try: stuff = os.stat(filename) return 1 except OSError: return None return None def setDefaultOpts(self): """Sets default set of options so that the structure is not empty.""" self.opts = {} if os.sep == '\\': # Ugly hack for windows. Some versions of windows # yield c:\ for the home directory, and some return something # really screwed up like \ or c\ even. self.opts['prefs_directory'] = "C:\\FORG-DATA" else: self.opts['prefs_directory'] = "%s%s%s" % (os.path.expanduser("~"), os.sep, ".forg") self.opts['home'] = "gopher://gopher.floodgap.com:70/1/" if not self.exists(self.opts['prefs_directory']): os.mkdir(self.opts['prefs_directory']) self.opts['use_cache'] = 1 self.opts['delete_cache_on_exit'] = None self.opts['use_url_format'] = 1 self.opts['grab_resource_info'] = None self.opts['show_host_port'] = None self.opts['save_options_on_exit'] = 1 self.opts['strip_carraige_returns'] = 1 self.opts['show_cached'] = None self.opts['display_info_in_directories'] = None self.opts['use_PIL'] = 1 # Use PIL for images basedir = self.getOption('prefs_directory') pkey = 'prefs_directory' self.opts['cache_directory'] = "%s%s%s" % (self.opts[pkey], os.sep, "cache") if not self.exists(self.opts['cache_directory']): os.mkdir(self.opts['cache_directory']) self.opts['cache_prefix'] = "%s%s" % (self.opts['cache_directory'], os.sep) return None def makeToggleWrapper(self, keyname): """Returns a function which when called with no arguments toggles the value of keyname within the options structure. This is used for menu callbacks connected to check buttons.""" def toggle_wrapper(opts=self, key=keyname): return opts.toggle(key) return toggle_wrapper def setOption(self, optionname, optionvalue): """Set optionname to optionvalue""" self.opts[optionname] = optionvalue return self.getOption(optionname) def getOption(self, optionname): """Get an option named optionname.""" try: optionname = lower(optionname) return self.opts[optionname] except KeyError: return None def toString(self): """Returns string representation of the object.""" return self.__str__() def __repr__(self): return self.__str__() def __str__(self): # God I love the map() function. lines = map(lambda x, self=self: "%s = %s" % (x, self.opts[x]), self.opts.keys()) comments = "%s%s%s" % ("# Options for the FORG\n", "# Please don't edit me unless you know what\n", "# you're doing.\n") return comments + join(lines, "\n") + "\n" def parseFile(self, filename): """Parse filename into a set of options. Caller is responsible for catching IOError related to reading files.""" print "Previously had %d keys" % len(self.opts.keys()) self.setDefaultOpts() fp = open(filename, "r") line = fp.readline() line_num = 0 while line != '': line_num = line_num + 1 commentIndex = find(line, "#") if commentIndex != -1: line = line[0:commentIndex] line = strip(line) if line == '': # Nothing to grokk line = fp.readline() # Get next line... continue items = split(line, "=") if len(items) < 2: print "Options::parseFile: no '=' on line number %d" % line_num line = fp.readline() # Get next line... continue if len(items) > 2: print("Options::parseFile: too many '=' on line number %d" % line_num) line = fp.readline() # Get next line... continue key = lower(strip(items[0])) # Normalize and lowercase val = lower(strip(items[1])) # Figure out what the hell val should be if val == 'no' or val == 'none' or val == 0 or val == '0': val = None self.opts[key] = val line = fp.readline() return self # Here is one instance of an options structure. # This is used by TkGui and other modules as a centralized place # to store program options. program_options = Options() forg-0.5.1.orig/Question.py0100644000175000017500000001003407344462645016037 0ustar jgoerzenjgoerzen# Question.py # Written by David Allen # Released under the terms of the GNU General Public License # $Id: Question.py,v 1.6 2001/07/11 22:43:09 s2mdalle Exp $ # Represents one question inside of a multi-question ASK block # # 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 string import * QuestionException = "Error dealing with Question" class Question: RESPONSE_ONE_LINE = 1 RESPONSE_PASSWORD = 2 RESPONSE_MULTI_LINE = 3 RESPONSE_FILENAME = 4 RESPONSE_CHOICES = 5 verbose = None def __init__(self, data=""): self.qtype = None self.promptString = "%s%s" % ("Answer the nice man's question,\n", "and nobody gets hurt.") self.default = "" self.setData(data) return None def getDefault(self): return self.default def setDefault(self, newdefault): self.default = newdefault return self.default def getPromptString(self): return self.promptString def getType(self): if self.verbose: print "QTYPE is ", self.qtype return self.qtype def setData(self, data): """Given data on a question in ASK format, this parses the data and sets the internal data of the object correctly. This should be done pretty much first after creating the object.""" self.linedata = data[:] # Copy ind = find(data, ":") if ind == -1: raise QuestionException, "Cannot find \":\" on line" qtype = strip(data[0:ind]) data = data[ind+1:] try: self.qtype = questions[qtype] except KeyError: raise(QuestionException, "Question type \"%s\" is not valid" % qtype) # Do the rest here... if (self.qtype == QUESTION_ASK or self.qtype == QUESTION_ASKL or self.qtype == QUESTION_ASKP): if find(data, "\t") != -1: try: [promptStr, default_val] = split(data, "\t") except: raise QuestionException, "Too many tabs in line" self.promptString = strip(promptStr) self.default = default_val if self.verbose: print "Block has default of ", self.default else: self.promptString = strip(data) elif self.qtype == QUESTION_ASKP: pass elif (self.qtype == QUESTION_ASKL or self.qtype == QUESTION_ASKF or self.qtype == QUESTION_CHOOSEF or self.qtype == QUESTION_NOTE): self.promptString = strip(data) elif self.qtype == QUESTION_CHOOSE or self.qtype == QUESTION_SELECT: try: ind = find(data, "\t") prompt = data[0:ind] opts = split(data[ind+1:], "\t") except : raise QuestionException, "Too many tabs in line" self.promptString = strip(prompt) self.options = opts self.default = self.options[0] else: raise QuestionException, "Unknown QType on parse" if self.verbose: print "Successfully parsed data line: ", self.linedata return None forg-0.5.1.orig/README0100644000175000017500000000751207344462645014545 0ustar jgoerzenjgoerzenTHE FORG Written by David Allen http://opop.nols.com/ Released under the terms of the GNU General Public License. Please see the file COPYING or visit http://www.gnu.org/ for more details. One of the things that the COPYING file is going to tell you is that I am not responsible for anything that may happen as a result of running this program. Please read the section "IN ORDER TO RUN THE FORG" below on how to get this running. (Hint: python, Tkinter, and Pmw, and you're there) FORG is a graphical gopher client. To run the program, run: ./forg.py host as an example, host can be "gopher.floodgap.com", "gopher.heatdeath.org", or "gopher.quux.org", which are all 3 valid gopher sites. Alternately, you can use URL syntax, as in: ./forg gopher://gopher.quux.org ---=== Features ===--- Everything listed here actually does work, well at least most of the time. Please see the BUGS file to find out about what doesn't work. - Ability to load other programs with different file formats. I.e. this program does not interpret HTML, so you may want to associate .html files with mozilla, so it will be launched upon saving a .html file. - Full caching of directories and files - Searching supported. (Tested against gopher.floodgap.com's Veronica, but of course) - Bookmark support. Bookmarks are written in XBEL and support arbitrary folders and subfolders. - Bookmark editing, similar to netscape - Directories are searchable by name. - Statistics on size of cache, number of files in the cache, and number of documents in the queue. - Ability to save files and directories. (Note: when directories are saved, the protocol information that the server sent is what is saved to disk...for now) - ASK menus - users can interract with programs on foreign servers through the use of miniature questionnaires. - Right click context menus on all items providing all available information on a resource. (If the server supports Gopher+) - Between 0 and 100% complete implementation of Gopher *AND* Gopher+!!! :) - Managerspeak. This program leverages automatic internet data retrieval and storaging technologies for quasi-reliable interlocked handshake protocol interaction, allowing open-ended failsafe solutions to be developed in the realm of...oh whatever. Please email me with information on anything that the program does that you don't think it should do. (Like penning filthy Russian novels, provoking arab/israeli hostilities, breaking your toaster, or causing a MIDI rendition of "Yankee Doodle" to be played backwards through your speakers) ---=== IN ORDER TO RUN THE FORG ===--- You will need 3 things. Python, Tkinter, and Pmw. If you have an up to date linux system, chances are good that you have python and Tkinter already. If you don't, you can download them from http://www.python.org/. The version of python required is 1.5.2, and the version of Tkinter required is the latest. Python version 2.0 is strongly suggested. To obtain Pmw, do one of several things: Pmw's page is located at: http://www.dscpl.com.au/pmw/ There you will find extensive instructions on downloading it and installing it. The instructions here should be sufficient though. Download the actual package from http://download.sourceforge.net/pmw/Pmw.0.8.5.tar.gz This is the latest version that was used for development. You could possibly skate by with an earlier version (I haven't tried it) but why? If you're using debian, you can just type "apt-get install python-pmw" and let the package management system do all of the dirty work for you. To install Pmw, just unzip it into your python's library directory. For most people, this will be something like /usr/lib/python1.5/site-packages/ or something similar. Theoretically, this program should run under windows. I haven't tried it, so don't be suprised if it dies horribly. But do email me. forg-0.5.1.orig/README.WINDOWS.txt0100644000175000017500000000134707344462645016514 0ustar jgoerzenjgoerzenWindows users: This program has been successfully run on windows before, but it is not guaranteed that all flavors will work. If you have any problems running this program, please mail me any error message or traceback that the program gives you in the output window. This will allow me to find and fix the problem if I can. Note that right now, unless you're pretty brave, the forg under windows is really only meant for python hackers. It's useable under UNIX, but due to some differences, the program can be quite flaky under windows. Please include information on what version of python you are using, and which flavor of windows you are running (95/98/NT/2000/Windows ME, etc) Please mail all problems and bugs to forg-0.5.1.orig/ResourceInformation.py0100644000175000017500000001461307344462645020234 0ustar jgoerzenjgoerzen# ResourceInformation.py # $Id: ResourceInformation.py,v 1.6 2001/07/05 17:16:53 s2mdalle Exp $ # Written by David Allen # Released under the terms of the GNU General Public License # # When dealing with a Gopher+ server, information about a document can be # fetched by sending the request: # some_locator\t!\r\n # # This module handles the parsing and storing of that information. # # 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 string import * from Tkinter import * import Pmw import os import re import ContentFrame from gopher import * import GopherResource import GopherResponse class ResourceInformation: verbose = None def __init__(self, data=None): self.blockdict = {} self.data = data if self.data != None: self.setData(self.data) return None def __str__(self): return self.toString() def toString(self): def fn(key, obj=self): return "%s:\n%s\n" % (upper(key), obj.getBlock(key)) return join(map(fn, self.getBlockNames()), "") + "\n" def setData(self, data): self.data = data self.data = re.sub("\r\n", "\n", self.data) lastindex = -1 blocks = [] try: while 1: # This will throw ValueError if not found. newindex = index(self.data, "\n+", (lastindex + 1), len(self.data)) blocks.append(self.data[lastindex+1:newindex]) lastindex = newindex except ValueError: # When no more "\n+" are found. # The remaining block is what's left... blocks.append(self.data[lastindex+1:len(self.data)]) # What we're going to do is hash each data block in by its block # 'title'. This way, when we're done hashing all of the data, we # can just go through and pick out what we want. So this type of # data: # +ADMIN: # Grendel The Evil # Some more admin information # Gets hashed in like this essentially: # hash['admin'] = "Grendel The Evil \n..." self.blockdict = {} # We now have a list of blocks. for block in blocks: lines = split(block, "\n") blocklabel = lines[0] front = find(blocklabel, "+") # This defines the start of a block back = find(blocklabel, ":") # This may not be present if front != -1: if back == -1: back = len(blocklabel) # End of string if not present # Get the name, which is like this: "+ADMIN:" => 'ADMIN' blockname = blocklabel[front+1:back] key = lower(blockname) # Lowercase so it hashes nicely. :) # strip the leading space. This is because in gopher+ # when it responds to info queries, each response line that # isn't a block header is indented by one space. data = re.sub("\n ", "\n", join(lines[1:], "\n")) # Get the first space in the data. if self.verbose: print "Data is %s" % data # Watch out for the case when data is ''. This could be in # particular if the server sends us a size packet like this: # +-1\r\n # Which would have a '' data segment. if data != '' and data[0] == ' ': data = data[1:] # Assign it into the hash. if self.verbose: print "Assigned data to key %s" % key if data != '' and not data is None: # No sense in assigning nothing into a key. The getBlock() # handles when there is no data and returns '' self.blockdict[key] = data else: print "BLOCK ERROR: cannot find blockname in %s" % blocklabel if self.verbose: k = self.blockdict.keys() print "Available block titles are:\n%s" % join(k, "\n") print "Keys are ", self.blockdict.keys() return self # Simple accessors/mutators. # Sure, I could just monkey around with the data in an object from outside, # but in some cultures, people are executed for such offenses against the # OOP paradigm. :) def setBlock(self, blockname, blockval): self.blockdict[lower(blockname)] = blockval return self.getBlock(lower(blockname)) def setInfo(self, newinfo): self.blockdict['info'] = newinfo return self.getInfo() def setAdmin(self, newadmin): self.blockdict['admin'] = newadmin return self.getAdmin() def setViews(self, newviews): self.blockdict['views'] = newviews return self.getViews() def setAbstract(self, newabstract): self.blockdict['abstract'] = newabstract return self.getAbstract() def getAbstract(self): return self.blockdict['abstract'] def getViews(self): return self.blockdict['views'] def getInfo(self): return self.blockdict['info'] def getAdmin(self): return self.blockdict['admin'] def getBlockNames(self): return self.blockdict.keys() def getBlock(self, blockname): try: return self.blockdict[lower(blockname)] except KeyError: return '' class GUIResourceInformation(Pmw.TextDialog): def __init__(self, resource_info_object): Pmw.TextDialog.__init__(self, title="Resource Information") self.insert('end', resource_info_object.toString()) return None forg-0.5.1.orig/State.py0100644000175000017500000000421307344462645015312 0ustar jgoerzenjgoerzen# State.py # $Id: State.py,v 1.2 2001/07/11 22:43:09 s2mdalle Exp $ # Written by David Allen # # Saves state information about a particular GopherResource. # This is a bit overkill, but it was easier than putting the information # in a tuple and then having to worry about which index gets to which item # in the tuple. Easier just to call methods like getResource() :) # # This is just a souped up structure. No calculation is done, just accessors # and mutators are provided to bundle several different data items together # under one object. # # 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. ############################################################################ class State: verbose = None def __init__(self, response, resource, widget): self.response = response self.resource = resource self.widget = widget return None def __str__(self): return "%s" % self.resource.toURL() def __repr__(self): return self.__str__() # Accessors def getResponse(self): return self.response def getResource(self): return self.resource def getWidget(self): return self.widget # Mutators def setResponse(self, resp): self.response = resp return self.getResponse() def setResource(self, res): self.resource = res return self.getResource() def setWidget(self, widget): self.widget = widget return self.getWidget() forg-0.5.1.orig/TODO0100644000175000017500000000155707344462645014360 0ustar jgoerzenjgoerzen- Optionally make use of PIL if it is installed (Python Imaging Library). I don't know how well this is going to work across platforms though. This could be used to display jpeg/gif/png when downloaded rather than using a launching program. - Maybe work in an alternative to GUIDirectory for listing things in an icon-list fashion. - Add Options editor. (See Options.py -- there are a number of configurable options, but no way to do that yet) - Improve associations editor. - Make it irrelevant whether users enter URLs or hostnames for starting points. (This will probably disallow interpretation as a local flie) - Support bookmark subfolders - Write bookmark handling widget (add/delete/rename/folders, etc) - Do more with information from server on different resources. (i.e. more than just block separation and presenting it to the user in a dialog) forg-0.5.1.orig/TkGui.py0100644000175000017500000006760307344462645015271 0ustar jgoerzenjgoerzen# TkGui.py # $Id: TkGui.py,v 1.56 2001/07/14 23:50:49 s2mdalle Exp $ # Written by David Allen # Released under the terms of the GNU General Public License # # This handles the GUI of the program, displaying, bookmarks, etc. # This module is usually called by the starter script. All other modules # are usually invoked through this one as the GUI needs them. # # 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. ############################################################################ # Python-wide modules not specific to this program import os import string import sys import re from threading import * import time import pickle import socket # Just for errors # Data structures, connections, and the like. import utils import Options import GopherObject import ResourceInformation import GopherResource import GopherResponse import GopherConnection import Bookmark import Associations import Cache from gopher import * # GUI specific modules. from Tkinter import * import tkFileDialog # File save/open dialog import AssociationsEditor # GUI editor for association rules import forg # The guts of the entire application import BookmarkEditor # Editing bookmarks window import Pmw # Python Mega Widgets import Dialogs # FORG specific dialogs ContentException = "ContentException:" class TkGui(Tk): verbose = None MENU_FILE = 1 MENU_EDIT = 2 MENU_NAVIGATION = 3 MENU_BOOKMARKS = 4 MENU_OPTIONS = 5 MENU_HELP = 6 RED = '#FF0000' GREEN = '#00FF00' BLUE = '#0000FF' def __init__(self, URL=None): """Create an instance of the gopher program.""" Tk.__init__(self) self.loadOptions() # Load program options from disk self.loadBookmarks() # Load program bookmarks from disk self.createAssociations() # Load program associations self.options_filename = self.getPrefsDirectory() + os.sep + "options" # Load options into the root window and all subwindows. try: if utils.file_exists(self.options_filename): self.option_readfile(self.options_filename) except Exception, errstr: print("***Error loading options from %s: %s" % (self.options_filename, errstr)) if self.verbose: print "OPTIONS:\n%s" % Options.program_options.toString() self.minsize(550, 550) self.title("FORG v. %s" % forg.getVersion()) self.saveFileDialog = None self.DOWNLOAD_ME = None # Thread control variable self.CURRENTLY_DOWNLOADING = None # Are we downloading right now? self.LAUNCH_ME = None # Thread control variable self.WIDGET_ENTRY = 0 self.mainBox = Frame(self) self.mainBox.pack(fill='both', expand=1) self.make_menus() self.config(menu=self.menu) self.buttonBar = Frame(self.mainBox) self.navBox = Frame(self.mainBox) self.buttonBar.pack(fill='x', expand=0) self.navBox.pack(fill='x', expand=0) self.BACKWARD = Button(self.buttonBar, text='Back', command=self.goBackward) self.FORWARD = Button(self.buttonBar, text='Forward', command=self.goForward) # self.STOP = Button(self.buttonBar, text='Stop', # command=self.stop) self.RELOAD = Button(self.buttonBar, text='Reload', command=self.reload) self.HOME = Button(self.buttonBar, text='Home', command=self.goHome) self.BACKWARD.pack(side='left') self.FORWARD.pack(side='left') # self.STOP.pack(side='left') self.RELOAD.pack(side='left') self.HOME.pack(side='left') if Options.program_options.getOption('use_url_format'): self.urlLabel = Label(self.navBox, text="Location:") self.urlEntry = Entry(self.navBox) self.urlEntry.bind("", self.go) self.urlLabel.pack(side='left') self.urlEntry.pack(side='left', expand=1, fill='x') else: self.hostLabel = Label(self.navBox, text='Host:') self.hostEntry = Entry(self.navBox) self.resourceLabel = Label(self.navBox, text='Resource:') self.resourceEntry = Entry(self.navBox, text="/") self.resourceEntry.insert('end', '/') self.portLabel = Label(self.navBox, text='Port:') self.portEntry = Entry(self.navBox, width=5) self.portEntry.insert('end', '70') self.hostLabel.grid(row=0, col=0, sticky=W) self.hostEntry.grid(row=0, col=1, columnspan=2, sticky=W) self.resourceLabel.grid(row=0, col=3, sticky=W) self.resourceEntry.grid(row=0, col=4, columnspan=2, sticky=W) self.portLabel.grid(row=0, col=6, sticky=W) self.portEntry.grid(row=0, col=7, sticky=W) # No colspan: short box self.gobutton = Button(self.navBox, text='Go', command=self.go) if Options.program_options.getOption('use_url_format'): self.gobutton.pack(side='right') else: self.gobutton.grid(row=0, col=8, sticky=W) if URL != None: resource = GopherResource.GopherResource() resource.setURL(URL) else: resource = GopherResource.GopherResource() resource.setURL("gopher://gopher.quux.org/") resource.setTitle("QUUX.org Root") self.CONTENT_BOX = forg.FORG(parent_widget=self.mainBox, parent_object=self, resource=resource) self.messageBar = Pmw.MessageBar(self.mainBox, entry_width = 80, entry_relief='groove', labelpos = 'w', label_text = 'Status:') self.CONTENT_BOX.setMessageBar(self.messageBar) self.CONTENT_BOX.pack(expand=1, fill='both') self.messageBar.pack(expand=0, fill='x') utils.msg(self.messageBar, "Ready") # Call fn when the window is destroyed. self.protocol("WM_DELETE_WINDOW", self.destroy) self.setLocation(self.CONTENT_BOX.getResource()) return None def getCache(self): return Options.program_options.cache def stats(self, *args): return self.CONTENT_BOX.stats() def destroy(self, *args): """Overridden destroy method for the application. This does cleanup, particularly with the various living threads before destroying the GUI on screen""" self.DOWNLOAD_ME = "DIE" # Kill the two other threads with this self.LAUNCH_ME = "DIE" # 'message' to them. # They will see this. # Things that need to be done before we exit... if Options.program_options.getOption('save_options_on_exit'): # self.messageBar.message('state', "Saving program options...") print "Saving options on exit..." self.saveOptions() if Options.program_options.getOption('delete_cache_on_exit'): print "Deleting cache on exit..." Options.program_options.cache.deleteCacheNoPrompt() Tk.destroy(self) print "MT: Exit" def editAssociations(self, *args): """Callback: opens the associations editor and lets the users change which programs launch on which filetypes.""" x = AssociationsEditor.AssociationsEditor( self, Options.program_options.associations ) return None def notYetImplemented(self, *args): """Opens a dialog for the user to see that the feature hasn't been implemented yet.""" self.genericMessage( "Contribute to the Programmer Pizza Fund today!\n" + "Maybe that will help get this feature put into\n" + "the program!") return None def setAssociations(self, assocList): """Modifier: associates a list of associations usually from the AssociationsEditor to this object.""" Options.program_options.setAssociations(assocList) return None def createAssociations(self, *args): """Creates a set of default associations. These are mostly UNIX centric unless I change them and forget to update this docstring. :) """ Options.program_options.setAssociations(Associations.Associations()) filename = self.getPrefsDirectory() + os.sep + "forg-associations" self.loadAssociations(filename) if Options.program_options.getAssociations().isEmpty(): print "ADDING DEFAULT ASSOCIATIONS" # Add defaults if there isn't anything in the association list. images = [".gif", ".jpg", ".bmp", ".xpm", ".xbm", ".png", ".jpeg", ".tif", ".tiff" ] for item in images: Options.program_options.associations.addAssociation(item, "eeyes $1") browser_stuff = [".html", ".htm", ".css"] for item in browser_stuff: cmd = "netscape $1" Options.program_options.associations.addAssociation(item, cmd) Options.program_options.associations.addAssociation(".pdf", "xpdf $1") Options.program_options.associations.addAssociation(".ps", "gv $1") return None def genericMessage(self, str, title='Information:'): """Given a string, pops up a dialog with the appropriate title and content string.""" x = Dialogs.InformationDialog(self, str, title) return None def genericError(self, errstr, title='Error:'): """Given an error string, pops up a dialog with the same title and text, alerting the user to something or other.""" x = Dialogs.ErrorDialog(self, errstr, title) return None def find(self, *args): """Wrapper function for find. When the find feature is used, this gets called to call the find feature in the child widget.""" wid = self.CONTENT_BOX.navList.getCurrent().getData().getWidget() self._find_dialog = Dialogs.FindDialog(self, wid, self) return None def addBookmark(self, *args): """Callback: take the current content frame and add the resource associated to it to the bookmark list. The new bookmark is appended at the end of the bookmarks menu""" resource = self.CONTENT_BOX.getResource() foo = Bookmark.Bookmark(resource) # Save the bookmark... self.bookmarks.insert(Bookmark.BookmarkMenuNode(foo)) self.saveBookmarks() self.reloadBookmarks() return None def reloadBookmarks(self, *args): """Reloads bookmarks from disk, and replaces the current Bookmark menu with the new one loaded from disk.""" print "*** Reloading bookmarks from disk after edit." self.loadBookmarks() # self.bookmarks now holds the freshly edited copy of the bookmarks. # Create a new Tk menu based off of them. # Destroy the old menu and insert the new. self.bookmarkTkMenu = self.buildTkBookmarksMenu(self.menu, self.bookmarks) self.menu.delete(self.MENU_BOOKMARKS) self.menu.insert_cascade(index=self.MENU_BOOKMARKS, label=self.bookmarks.getName(), menu=self.bookmarkTkMenu) return None def getPrefsDirectory(self): return Options.program_options.getOption('prefs_directory') def loadAssociations(self, filename=None): if filename is None: filename = self.getPrefsDirectory() + os.sep + "forg-associations" try: Options.program_options.associations.loadFromFile(filename) except IOError, errstr: print "****Cannot load associations from %s: %s" % (filename, str) return 0 # Failure return 1 # Success def loadOptions(self, filename=None): if filename is None: filename = self.getPrefsDirectory() + os.sep + "forgrc" try: Options.program_options.parseFile(filename) except IOError, errstr: print "**** Couldn't parse options at %s: %s" % (filename, errstr) return None print "****Successfully loaded options from disk." return 1 def saveOptions(self, *args): """Saves the user options to a file in their home directory. Who knows what happens on windows boxen.""" filename = self.getPrefsDirectory() + os.sep + "forg-associations" try: Options.program_options.associations.writeToFile(filename) except IOError, str: print "***Error saving associations to disk: %s" % str ofilename = self.getPrefsDirectory() + os.sep + "forgrc" try: Options.program_options.save() except IOError, str: self.genericError("Couldn't write options to file:\n%s" % str) utils.msg(self.messageBar, "Finished saving options.") return None def loadBookmarks(self, *args): self.bookmarks = Bookmark.BookmarkMenu("Bookmarks") filename = self.getPrefsDirectory() + os.sep + "bookmarks" try: fp = open(filename, 'r') factory = Bookmark.BookmarkFactory() factory.parseResource(fp) self.bookmarks = factory.getMenu() except IOError, errstr: print "****Couldn't load bookmarks at %s: %s" % (filename, errstr) return None print "****Bookmarks successfully loaded from disk." return 1 def saveBookmarks(self, *args): filename = self.getPrefsDirectory() + os.sep + "bookmarks" try: fp = open(filename, "w") factory = Bookmark.BookmarkFactory() factory.writeXML(fp, self.bookmarks) fp.close() except IOError, errstr: print "****Couldn't save bookmarks to %s: %s" % (filename, errstr) return None print "****Bookmarks successfully saved to %s" % filename return 1 def stop(self, *args): """Just set green light to a false value, and wait for the download thread to get the point. It will quit once it sees this.""" return Options.program_options.redLight() def goForward(self, *args): self.CONTENT_BOX.goForward() self.setLocation(self.CONTENT_BOX.getResource()) def goBackward(self, *args): self.CONTENT_BOX.goBackward() self.setLocation(self.CONTENT_BOX.getResource()) def go(self, *rest): """This is what happens when the 'Go' button is clicked. Information about the host, port, locator is fetched from the entry boxes, and the program goes there. (Or tries, anyway)""" if Options.program_options.getOption('use_url_format'): url = string.strip(self.urlEntry.get()) ind = string.find(url, "://") if ind != -1: proto = url[0:ind] if string.lower(proto) != "gopher": self.genericError("Protocol\n\"%s\"\nnot supported." % proto) return None elif string.lower(url[0:9]) != "gopher://": url = "gopher://" + url res = GopherResource.GopherResource() try: res.setURL(url) except Exception, estr: self.genericError("Invalid gopher URL:\n%s\n%s" % url, estr) return None else: host = self.hostEntry.get() port = self.portEntry.get() locator = self.resourceEntry.get() if locator == '': locator = "/" if port == '' or port < 0: port = 70 res = GopherResource.GopherResource('1', host, port, locator, "%s Root" % host) # Either way, go there. self.goElsewhere(res) def goElsewhere(self, resource, usecache=1, *args): self.CONTENT_BOX.goElsewhere(resource, usecache, args) self.setLocation(self.CONTENT_BOX.getResource()) def openURL(self, URL): resource = GopherResource.GopherResource() resource.setURL(URL) return self.goElsewhere(resource) def showOpenURL(self, *args): d = Dialogs.OpenURLDialog(self, self.openURL) return None def saveFile(self, *args): return self.CONTENT_BOX.saveFile() def popupMenu(self, event): """Display pop-up menu on right click on a message""" self.popup.tk_popup(event.x_root, event.y_root) 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='Save', command=self.CONTENT_BOX.saveFile) self.popup.add_command(label='Back', command=self.CONTENT_BOX.goBackward) self.popup.add_command(label='Forward', command=self.CONTENT_BOX.goForward) def change_content_hook(self, resource): """This function is called by the child FORG instance whenever content is changed. The resource argument is the GopherResource object corresponding to the content currently in the window.""" return self.setLocation(resource) def setLocation(self, resource): """Takes a resource, and sets the location information at the top of the screen to the information in the resource. If you're going to a certain location, this gets called to update what the user sees as the location.""" if Options.program_options.getOption('use_url_format'): URL = resource.toURL() # Translate spaces. This is ONLY done because this is the way # other programs like to get their URLs, not because it needs to # be this way per se. Users copy and paste out of this location # bar, so put a standard style URL there. URL = re.sub(" ", "%20", URL) self.urlEntry.delete(0, 'end') self.urlEntry.insert('end', URL) else: self.hostEntry.delete(0, 'end') self.resourceEntry.delete(0, 'end') self.portEntry.delete(0, 'end') self.hostEntry.insert('end', resource.getHost()) self.resourceEntry.insert('end', resource.getLocator()) self.portEntry.insert('end', resource.getPort()) return None def dumpQueue(self, *rest): return self.CONTENT_BOX.dumpQueue() def reload(self, *rest): return self.CONTENT_BOX.reload() def about(self, *args): return self.CONTENT_BOX.about() def quit(self, *args): """Quit the entire program. Caller may have something after this.""" self.destroy() def editBookmarks(self, *args): ed = BookmarkEditor.BookmarkEditor(self.bookmarks, ondestroy=self.reloadBookmarks) def buildTkBookmarksMenu(self, parent_menu, bookmarks): def fn(item, self=self): return self.goElsewhere(item) newTkMenu = bookmarks.getTkMenu(parent_menu, fn) newTkMenu.insert_separator(index=1) newTkMenu.insert_command(index=0, label="Edit Bookmarks", command=self.editBookmarks) newTkMenu.insert_command(index=1, label="Bookmark this page", command=self.addBookmark) return newTkMenu def goHome(self, *args): """Redirects the content of the main window to the user's chosen home site.""" url = Options.program_options.getOption('home') if url: res = GopherResource.GopherResource() res.setURL(url) return self.goElsewhere(res) else: d = Dialogs.ErrorDialog(self, "You don't have a home site specified", "Error: Cannot locate home site") return None def setHome(self, *args): url = self.CONTENT_BOX.getResource().toURL() Options.program_options.setOption('home', url) return None def make_menus(self): """Create the menuing system""" self.menu = Menu(self.mainBox) self.filemenu = Menu(self.menu) self.filemenu.add_command(label='Open URL', command=self.showOpenURL) self.filemenu.add_command(label='Save', command=self.saveFile) self.filemenu.add_command(label='Quit', underline=0, command=self.quit) self.editmenu = Menu(self.menu) self.editmenu.add_command(label="Find", command=self.find) self.navmenu = Menu(self.menu) self.navmenu.add_command(label="Forward", command=self.goForward) self.navmenu.add_command(label="Backward", command=self.goBackward) self.navmenu.add_command(label="Reload", command=self.reload) self.optionsmenu = Menu(self.menu) self.optionsmenu.add_command(label='Associations', command=self.editAssociations) self.optionsmenu.add_command(label="Save Options/Associations", command=self.saveOptions) self.optionsmenu.add_command(label="Reload Options/Associations", command=self.loadOptions) self.optionsmenu.add_command(label="Set This Site as My Home", command=self.setHome) def purgeCacheWrapper(c=Options.program_options.cache, parent=self): c.emptyCache(parent) self.omenucache = Menu(self.optionsmenu) self.omenucache.add_command(label="Purge Cache", command=purgeCacheWrapper) self.omenucache.add_command(label="Cache Statistics", command=self.stats) self.optionsmenu.add_cascade(label="Cache", menu=self.omenucache) # Default value of 'show_cached' scstatus = Options.program_options.getOption('show_cached') # Function to toggle the value of 'show_cached' sccallback = Options.program_options.makeToggleWrapper('show_cached') # key = 'grab_resource_info' # gristatus = Options.program_options.getOption(key) # gricallback = Options.program_options.makeToggleWrapper(key) key = "delete_cache_on_exit" dcstatus = Options.program_options.getOption(key) dccallback = Options.program_options.makeToggleWrapper(key) key = "use_PIL" upstatus = Options.program_options.getOption(key) upcallback = Options.program_options.makeToggleWrapper(key) key = "save_options_on_exit" sooestatus = Options.program_options.getOption(key) sooecallback = Options.program_options.makeToggleWrapper(key) key = "strip_carraige_returns" scrstatus = Options.program_options.getOption(key) scrcallback = Options.program_options.makeToggleWrapper(key) key = "use_cache" ucstatus = Options.program_options.getOption(key) uccallback = Options.program_options.makeToggleWrapper(key) # This is a dictionary to hold the IntVar data that gets used to set # the default value of the checkbuttons. Because of some weirdness, # if the data isn't held, then it does'nt work properly. (I.e. if you # use a variable only within this following loop to create an IntVar # and don't store it outside of the temporary loop, it won't work) # So in other words this is data getting put into a quick dictionary # "Just Because" and isn't actually going to be used. # If you want to see what I'm talking about, try replacing # self.followvars with just 'var' as a temporary variable, and it # won't work. self.followvars = {} for item in [["Show Cached Status", scstatus, sccallback], ["Save Options On Exit", sooestatus, sooecallback], ["Strip Carriage Returns", scrstatus, scrcallback], ["Use Cache", ucstatus, uccallback], ["Delete Cache on Exit", dcstatus, dccallback], ["Use PIL if Available", upstatus, upcallback]]: # Store this variable because python is weird. self.followvars[item[0]] = IntVar() if item[1]: self.followvars[item[0]].set(1) else: self.followvars[item[0]].set(0) self.optionsmenu.add_checkbutton(label=item[0], indicatoron=TRUE, # Use the stored varable here... # bind it to the menu variable=self.followvars[item[0]], command=item[2]) try: if self.bookmarkTkMenu: self.bookmarkTkMenu = None except: pass self.bookmarkTkMenu = self.buildTkBookmarksMenu(self.menu, self.bookmarks) self.helpmenu = Menu(self.menu) self.helpmenu.add_command(label='About', command=self.about) self.hdebug = Menu(self.helpmenu) def printopts(opts=Options.program_options): print opts self.hdebug.add_command(label="Dump Options", command=printopts) self.hdebug.add_command(label="Dump Queue", command=self.dumpQueue) self.helpmenu.add_cascade(label='Debug', menu=self.hdebug) for menu_node in [self.filemenu, self.editmenu, self.navmenu, self.bookmarkTkMenu, self.optionsmenu, self.helpmenu]: # Disable menu tearoffs. menu_node['tearoff'] = FALSE self.menu.add_cascade(label="File", menu=self.filemenu) self.menu.add_cascade(label="Edit", menu=self.editmenu) self.menu.add_cascade(label="Navigation", menu=self.navmenu) self.menu.add_cascade(label="Bookmarks", menu=self.bookmarkTkMenu) self.menu.add_cascade(label="Options", menu=self.optionsmenu) self.menu.add_cascade(label="Help", menu=self.helpmenu) # END TkGui class forg-0.5.1.orig/Tree.py0100644000175000017500000010646607344462645015146 0ustar jgoerzenjgoerzen# Tree.py # highly optimized tkinter tree control # Written by Charles E. "Gene" Cash # Modifications by David Allen # # 98/12/02 CEC started # 99/??/?? CEC release to comp.lang.python.announce # # to do: # add working "busy" cursor # make paste more general # add good ideas from win95's tree common control # # 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. ############################################################################ import os import string import Tkdnd import Bookmark import ListNode import List import BookmarkEditor import Dialogs from Tkinter import * # this is initialized later, after Tkinter is started class Icons: OPEN_ICON = None SHUT_ICON = None FILE_ICON = None #------------------------------------------------------------------------------ # cut'n'paste helper object class Cut_Object: def __init__(self, node=None, id=None, state=None): self.node = node self.full_id = id self.state = state def getNode(self): return node def getFullId(self): return self.full_id def getState(self): return state #------------------------------------------------------------------------------ # tree node helper class class NodeData: """This is the same as Node, but only holds the data and does nothing with it. This is ideal for creating nodes that you don't want drawn until they get pasted in via a Cut_Object""" def __init__(self, parent, name, id, closed_icon, open_icon, x, y, parentwidget): self.parent = parent self.name = name self.id = id self.open_icon = open_icon self.closed_icon = closed_icon self.widget = parentwidget self.subnodes = [] self.spinlock = 0 self.openflag = 0 return None class Node: # initialization creates node, draws it, and binds mouseclicks def __init__(self, parent, name, id, closed_icon, open_icon, x, y, parentwidget): # lots of things to remember # immediate parent node self.parent = parent self.__x = x self.__y = y # name displayed on the label self.name = name # internal name used to manipulate things self.id = id # bitmaps to be displayed self.open_icon = open_icon self.closed_icon = closed_icon # tree widget we belong to self.widget = parentwidget # our list of child nodes self.subnodes = [] # cheap mutex spinlock self.spinlock = 0 # closed to start with self.open_flag = 0 # call customization hook if self.widget.init_hook: self.widget.init_hook(self) def drawNode(self): # draw horizontal connecting lines if self.widget.lineflag: self.line = self.widget.create_line(self.__x-self.widget.distx, self.__y, self.__x, self.__y) # draw approprate image self.symbol = self.widget.create_image(self.__x, self.__y, image=self.closed_icon) # add label self.label = self.widget.create_text(self.__x+self.widget.textoff, self.__y, text=self.name, justify='left', anchor='w') def infoCallback(event, s=self): display_information(s, s.widget) self.widget.tag_bind(self.label, '', infoCallback) # single-click to expand/collapse cmd = self.widget.tag_bind(self.symbol, '<1>', self.click) # drag'n'drop support if self.widget.dnd_hook: # this starts the drag operation self.widget.tag_bind(self.label, '<1>', lambda ev, qs=self:qs.widget.dnd_click(ev, qs)) # these events help identify drop target node self.widget.tag_bind(self.symbol, '', lambda ev, qs=self:qs.widget.enter(qs)) self.widget.tag_bind(self.label, '', lambda ev, qs=self:qs.widget.enter(qs)) self.widget.tag_bind(self.symbol, '', self.widget.leave) self.widget.tag_bind(self.label, '', self.widget.leave) elif self.widget.item_dbl_click_hook: self.widget.tag_bind(self.label, '', self.widget.item_dbl_click_hook) def isFile(self): return (self.children() == 0 and (self.open_icon == None or self.open_icon == Icons.FILE_ICON)) def isFolder(self): return not self.isFile() def children(self): return len(self.subnodes) def __repr__(self): try: # Parent may be None so this may croak. pname = self.parent.name except: pname = "NONEXISTANT" return 'Node: %s Parent: %s (%d children)' % \ (self.name, pname, len(self.subnodes)) # recursively delete subtree & clean up cyclic references def _delete(self): for i in self.subnodes: if i.open_flag and i.subnodes: # delete vertical connecting line if self.widget.lineflag: self.widget.delete(i.tree) # delete node's subtree, if any i._delete() # the following unbinding hassle is because tkinter # keeps a callback reference for each binding # so if we want things GC'd... # # MDA: 7/5/2001: Commented this out because it breaks with # Python 2.1's default Tkinter as _tagcommands doesn't exist. # Followup. # for j in (i.symbol, i.label): # for k in self.widget.tag_bind(j): # self.widget.tag_unbind(j, k) # for k in self.widget._tagcommands.get(j, []): # self.widget.deletecommand(k) # self.widget._tagcommands[j].remove(k) # delete widgets from canvas self.widget.delete(i.symbol, i.label) if self.widget.lineflag: self.widget.delete(i.line) # break cyclic reference i.parent=None # move cursor if it's in deleted subtree if self.widget.pos in self.subnodes: self.widget.move_cursor(self) # now subnodes will be properly garbage collected self.subnodes = [] # move everything below current icon, to make room for subtree # using the magic of item tags def _tagmove(self, dist): # mark everything below current node as movable bbox1 = self.widget.bbox(self.widget.root.symbol, self.label) bbox2 = self.widget.bbox('all') self.widget.dtag('move') self.widget.addtag('move', 'overlapping', bbox2[0], bbox1[3], bbox2[2], bbox2[3]) # untag cursor & node so they don't get moved too # this has to be done under Tk on X11 self.widget.dtag(self.widget.cursor_box, 'move') self.widget.dtag(self.symbol, 'move') self.widget.dtag(self.label, 'move') # now do the move of all the tagged objects self.widget.move('move', 0, dist) # fix up connecting lines if self.widget.lineflag: n = self while n: if len(n.subnodes) and n.subnodes[-1] is not None: # position of current icon x1, y1 = self.widget.coords(n.symbol) # position of last node in subtree x2, y2 = self.widget.coords(n.subnodes[-1:][0].symbol) self.widget.coords(n.tree, x1, y1, x1, y2) n = n.parent # return list of subnodes that are expanded (not including self) # only includes unique leaf nodes (e.g. /home and /home/root won't # both be included) so expand() doesn't get called unnecessarily # thank $DEITY for Dr. Dutton's Data Structures classes at UCF! def expanded(self): # push initial node into stack stack = [(self, (self.id,))] list = [] while stack: # pop from stack p, i = stack[-1:][0] del stack[-1:] # flag to discard non-unique sub paths flag = 1 # check all children for n in p.subnodes: # if expanded, push onto stack if n.open_flag: flag = 0 stack.append(n, i+(n.id,)) # if we reached end of path, add to list if flag: list.append(i[1:]) return list # get full name, including names of all parents def full_id(self): if self.parent: return self.parent.full_id()+(self.id,) else: return (self.id,) # expanding/collapsing folders def toggle_state(self, state=None): if not self.open_icon: # not a expandable folder return if state == None: # toggle to other state state = not self.open_flag else: # are we already in the state we want to be? if (not state) == (not self.open_flag): return # not re-entrant # acquire mutex while self.spinlock: pass self.spinlock = 1 # call customization hook if self.widget.before_hook: self.widget.before_hook(self) # if we're closed, expand & draw our subtrees if not self.open_flag: self.open_flag = 1 self.widget.itemconfig(self.symbol, image=self.open_icon) # get contents of subdirectory or whatever contents = self.widget.get_contents(self) # move stuff to make room self._tagmove(self.widget.disty*len(contents)) # now draw subtree self.subnodes = [] # get current position of icon x, y = self.widget.coords(self.symbol) yp = y for i in contents: try: print "CONTENTS: i is %s class %s" % (i, i.__class__) except: pass # add new subnodes, they'll draw themselves yp = yp+self.widget.disty newnode = Node(self, i[0], i[1], i[2], i[3], x+self.widget.distx, yp, self.widget) newnode.drawNode() self.subnodes.append(newnode) # the vertical line spanning the subtree if self.subnodes and self.widget.lineflag: _tval = y+self.widget.disty*len(self.subnodes) self.tree = self.widget.create_line(x, y, x, _tval) self.widget.lower(self.tree, self.symbol) # if we're open, collapse and delete subtrees elif self.open_flag: self.open_flag = 0 self.widget.itemconfig(self.symbol, image=self.closed_icon) # if we have any children if self.subnodes: # recursively delete subtree icons self._delete() # delete vertical line if self.widget.lineflag: self.widget.delete(self.tree) # find next (vertically-speaking) node n = self while n.parent: # position of next sibling in parent's list i = n.parent.subnodes.index(n)+1 if i < len(n.parent.subnodes): n = n.parent.subnodes[i] break n = n.parent if n.parent: # move everything up so that distance to next subnode is # correct x1, y1 = self.widget.coords(self.symbol) x2, y2 = self.widget.coords(n.symbol) dist = y2-y1-self.widget.disty self._tagmove(-dist) # update scroll region for new size x1, y1, x2, y2 = self.widget.bbox('all') self.widget.configure(scrollregion=(x1, y1, x2+5, y2+5)) # call customization hook if self.widget.after_hook: self.widget.after_hook(self) # release mutex self.spinlock = 0 def expandAll(self): self.toggle_state(1) # Expand this item for n in self.subnodes: # Recursively expand subnodes. n.expandAll() return None # expand this subnode # doesn't have to exist, it expands what part of the path DOES exist def expand(self, dirs): # if collapsed, then expand self.toggle_state(1) # find next subnode if dirs: for n in self.subnodes: if n.id == dirs[0]: n.expand(dirs[1:]) break # handle mouse clicks by moving cursor and toggling folder state def click(self, event): self.widget.move_cursor(self) self.toggle_state() # cut a node and it's subtree def cut(self): # remember what was expanded, so we can re-expand on paste expand_list = self.expanded() if not self.open_flag: expand_list = None id = self.full_id() # collapse self.toggle_state(0) # delete from tree if self.parent: # Remove all data from the parent's BookmarkMenu # print "Removing reference from \"%s\" to \"%s\"" % ( # self.parent.id, self.id) # print "Class of removee is ", self.id.__class__ self.parent.id.removeReference(self.id) # move cursor safely out of the way if self.widget.pos == self: self.widget.prev() if len(self.parent.subnodes) == 1: # delete vertical connecting line # if we're the only child if self.widget.lineflag: self.widget.delete(self.parent.tree) # delete from parent's list of children self.parent.subnodes.remove(self) # move rest of tree up self._tagmove(-self.widget.disty) # break cyclic reference self.parent = None # see _delete() for why we have to do this # MDA: 7/5/2001: Commented this out because it breaks with # Python 2.1's default Tkinter as _tagcommands doesn't exist. # Followup. # for j in (self.symbol, self.label): # for k in self.widget.tag_bind(j): # self.widget.tag_unbind(j, k) # for k in self.widget._tagcommands.get(j, []): # self.widget.deletecommand(k) # self.widget._tagcommands[j].remove(k) # delete from canvas self.widget.delete(self.symbol, self.label) if self.widget.lineflag: self.widget.delete(self.line) # update scrollbar for new height x1, y1, x2, y2 = self.widget.bbox('all') self.widget.configure(scrollregion=(x1, y1, x2+5, y2+5)) # returns a "cut_object" co = Cut_Object(self, id, expand_list) # call customization hook if self.widget.cut_hook: self.widget.cut_hook(co) return co # insert a "cut object" at the proper place # option: # 1 - insert as 1st child # 2 - insert after last child # 3 - insert as next sibling def paste(self, co, option=1): # call customization hook # this usually does the actual cut'n'paste on the underlying # data structure if self.widget.paste_hook: # if it returns false, it wasn't successful if self.widget.paste_hook(co): return self.expand([1]) # expand if necessary # if option == 1 or option == 2: # self.toggle_state(2) # Uncomment if lineflag to Tree() was true. #if option == 1 or option == 2 and not self.subnodes: # # Create the horizontal line if it isn't already present. # # (i.e. inserting into a dir that has 0 children) # # Warning: this is an ugly hack. # x, y = self.widget.coords(self.symbol) # _tval = y+self.widget.disty*len(self.subnodes) # self.tree = self.widget.create_line(x, y, x, _tval) # self.widget.lower(self.tree, self.symbol) # make subnode list the right size for _tagmove() if option == 1 or not self.parent: self.subnodes.insert(0, None) # Option is insert as first child, so prepend the Bookmark # to the BookmarkMenu self.id.prepend(ListNode.ListNode(co.node.id)) i = 0 elif option == 2: self.id.postpend(ListNode.ListNode(co.node.id)) self.subnodes.append(None) i = -1 elif option == 3: i = self.parent.subnodes.index(self)+1 # Update the BookmarkMenu relationship self.parent.id.insertAfter(newnode=ListNode.ListNode(co.node.id), afterWhat=self.id) # Update the list of GUI subnodes self.parent.subnodes.insert(i, None) # move rest of tree down self._tagmove(self.widget.disty) # place new node xval, yval = self.widget.coords(self.symbol) if option == 1 or option == 2: xval = xval+self.widget.distx yval = yval+self.widget.disty # create node if option == 1 or option == 2: node_parent = self elif option == 3: node_parent = self.parent n = Node(parent=node_parent, name=co.node.name, id=co.node.id, # co.node.open_flag, # co.node.icon, closed_icon=co.node.closed_icon, open_icon=co.node.open_icon, x=xval, y=yval, parentwidget=self.widget) n.drawNode() # insert into tree...if it's the root item, don't try to insert it as # a sibling. if option == 1 or option == 2 or not self.parent: self.subnodes[i] = n elif option == 3: # insert as next sibling self.parent.subnodes[i] = n # expand children to the same state as original cut tree if co.state: n.expand(co.state) # return next lower visible node def next(self): n = self if n.subnodes: # if you can go right, do so return n.subnodes[0] while n.parent: # move to next sibling i = n.parent.subnodes.index(n)+1 if i < len(n.parent.subnodes): return n.parent.subnodes[i] # if no siblings, move to parent's sibling n = n.parent # we're at bottom return self # return next higher visible node def prev(self): n = self if n.parent: # move to previous sibling i = n.parent.subnodes.index(n)-1 if i >= 0: # move to last child n = n.parent.subnodes[i] while n.subnodes: n = n.subnodes[-1] else: # punt if there's no previous sibling if n.parent: n = n.parent return n def display_information(node, parent_widget): """Displays information about the given node in a separate window that is a child of parent_widget. This is for when the user requests information about different tree items.""" data = node.id # Since python does everything call by reference, we can create a callback # function that merely edits the data of the node in place, and it will # apply to what's actually in the tree. # Pass this the original reference (in the tree) and the new resource # whose information matches what the stuff in the tree should be. def editReference(orig_reference, new_res, n=node): if orig_reference.__class__ == Bookmark.Bookmark: orig_reference.setURL(new_res.getURL()) orig_reference.setName(new_res.getName()) # Update the label on the widget n.widget.itemconfig(n.label, text=new_res.getName()) else: # It's a folder. orig_reference.setName(new_res) n.widget.itemconfig(n.label, text=new_res) return None # Ugly. NewBookmarkDialog takes a callback, and this is it. It will # pass the res argument - a newly created GopherResource object. # Call editReference on it instead with the required arguments. def editReferenceStub(res, ref=data, callback=editReference): return callback(orig_reference=ref, new_res=res) if data.__class__ == Bookmark.Bookmark: # Create a new bookmark dialog. Note this is the same that is used to # create new bookmarks, we're just specifying the callback to actually # change an existing one instead of create something new in the tree. e = Dialogs.NewBookmarkDialog(parent_widget, editReferenceStub, data) else: # Same as above (for bookmark dialogs) except this is for folder # names. e = Dialogs.NewFolderDialog(parent_widget, editReferenceStub, data.getName()) return None #------------------------------------------------------------------------------ # default routine to get contents of subtree # supply this for a different type of app # argument is the node object being expanded # should return list of 4-tuples in the form: # (label, unique identifier, closed icon, open icon) # where: # label - the name to be displayed # unique identifier - an internal fully unique name # closed icon - PhotoImage of closed item # open icon - PhotoImage of open item, or None if not openable def get_contents(node): """Returns the contents of a particular node""" def fn(node): """Just a function used for traversing the list inside of a BookmarkMenu object. See Bookmark.BookmarkMenu for information on how they are put together""" node = node.getData() if node.__class__ == Bookmark.BookmarkMenu: node_name = node.getName() tuple = (node_name, node, Icons.SHUT_ICON, Icons.OPEN_ICON) else: node_name = node.getName() tuple = (node_name, node, Icons.FILE_ICON, None) return tuple if node.id.__class__ == Bookmark.BookmarkMenu: l = node.id.traverse(fn) return l else: # It's a Bookmark.ListItem object tuple = (node.id.getData().getName(), node.id.getData(), Icons.FILE_ICON, None) return tuple #------------------------------------------------------------------------------ class Tree(Canvas): def __init__(self, master, datatree, rootlabel=None, openicon=None, shuticon=None, getcontents=get_contents, init=None, before=None, after=None, cut=None, paste=None, dnd=None, distx=15, disty=15, textoff=10, lineflag=1, **kw_args): self.data_tree = datatree self.cutbuffer = None # pass args to superclass apply(Canvas.__init__, (self, master), kw_args) # default images (BASE64-encoded GIF files) # we have to delay initialization until Tk starts up or PhotoImage() # complains (otherwise I'd just put it up top) if Icons.OPEN_ICON == None: Icons.OPEN_ICON = PhotoImage( data='R0lGODlhEAANAKIAAAAAAMDAwICAgP//////ADAwMAAAAAAA' \ 'ACH5BAEAAAEALAAAAAAQAA0AAAM6GCrM+jCIQamIbw6ybXNSx3GVB' \ 'YRiygnA534Eq5UlO8jUqLYsquuy0+SXap1CxBHr+HoBjoGndDpNAAA7') Icons.SHUT_ICON = PhotoImage( data='R0lGODlhDwANAKIAAAAAAMDAwICAgP//////ADAwMAAAAAAA' \ 'ACH5BAEAAAEALAAAAAAPAA0AAAMyGCHM+lAMMoeAT9Jtm5NDKI4Wo' \ 'FXcJphhipanq7Kvu8b1dLc5tcuom2foAQQAyKRSmQAAOw==') Icons.FILE_ICON = PhotoImage( data='R0lGODlhCwAOAJEAAAAAAICAgP///8DAwCH5BAEAAAMALAAA' \ 'AAALAA4AAAIphA+jA+JuVgtUtMQePJlWCgSN9oSTV5lkKQpo2q5W+' \ 'wbzuJrIHgw1WgAAOw==') # function to return subnodes (not very much use w/o this) if not getcontents: raise ValueError, 'must have "get_contents" function' self.get_contents = getcontents # horizontal distance that subtrees are indented self.distx = distx # vertical distance between rows self.disty = disty # how far to offset text label self.textoff = textoff # self.item_dbl_click_hook = self.editNode self.item_dbl_click_hook = None # called after new node initialization self.init_hook = init # called just before subtree expand/collapse self.before_hook = before # called just after subtree expand/collapse self.after_hook = after # called at the end of the cut operation self.cut_hook = cut # called beginning of the paste operation self.paste_hook = paste # flag to display lines self.lineflag = lineflag # called at end of drag'n'drop operation # self.dnd_hook = dnd self.dnd_hook = None # create root node to get the ball rolling if openicon: oi = openicon else: oi = Icons.OPEN_ICON if shuticon: si = shuticon else: si = Icons.SHUT_ICON if not rootlabel: rootlabel = " " self.root = Node(parent=None, name=rootlabel, id=self.data_tree, closed_icon=si, open_icon=oi, x=10, y=10, parentwidget=self) self.root.drawNode() # configure for scrollbar(s) x1, y1, x2, y2 = self.bbox('all') self.configure(scrollregion=(x1, y1, x2+5, y2+5)) # add a cursor self.cursor_box = self.create_rectangle(0, 0, 0, 0) self.move_cursor(self.root) # make it easy to point to control self.bind('', self.mousefocus) # totally arbitrary yet hopefully intuitive default keybindings # page-up/page-down self.bind('', self.pagedown) self.bind('', self.pageup) # arrow-up/arrow-down self.bind('', self.next) self.bind('', self.prev) # arrow-left/arrow-right self.bind('', self.ascend) # (hold this down and you expand the entire tree) self.bind('', self.descend) # home/end self.bind('', self.first) self.bind('', self.last) # space bar self.bind('', self.toggle) def destroy(self, *args): # Garbage collect the image objects. If this isn't done, # Tkinter holds onto a useless reference to them which can cause # a stderr message. Icons.OPEN_ICON = None Icons.SHUT_ICON = None Icons.FILE_ICON = None # Superclass destructor Canvas.destroy(self) return None def editNode(self, node): pass # scroll (in a series of nudges) so items are visible def see(self, *items): x1, y1, x2, y2 = apply(self.bbox, items) while x2 > self.canvasx(0)+self.winfo_width(): old = self.canvasx(0) self.xview('scroll', 1, 'units') # avoid endless loop if we can't scroll if old == self.canvasx(0): break while y2 > self.canvasy(0)+self.winfo_height(): old = self.canvasy(0) self.yview('scroll', 1, 'units') if old == self.canvasy(0): break # done in this order to ensure upper-left of object is visible while x1 < self.canvasx(0): old = self.canvasx(0) self.xview('scroll', -1, 'units') if old == self.canvasx(0): break while y1 < self.canvasy(0): old = self.canvasy(0) self.yview('scroll', -1, 'units') if old == self.canvasy(0): break # move cursor to node def move_cursor(self, node): self.pos = node x1, y1, x2, y2 = self.bbox(node.symbol, node.label) self.coords(self.cursor_box, x1-1, y1-1, x2+1, y2+1) self.see(node.symbol, node.label) # expand given path # note that the convention used in this program to identify a # particular node is to give a tuple listing it's id and parent ids def expand(self, path): self.root.expand(path[1:]) def setCutBuffer(self, newcutbuffer): self.cutbuffer = newcutbuffer return self.cutbuffer def getCutBuffer(self): return self.cutbuffer def getRoot(self): return self.root def cut(self): """Tree.cut: cut the selected node""" active = self.getActive() if active != self.root: # Don't allow the user to cut the root node self.cutbuffer = self.getActive().cut() return None def expandAll(self): return self.root.expandAll() def paste(self): if self.cutbuffer: if self.getActive().isFolder() and self.cutbuffer.node.isFolder(): # If we're pasting a folder into a folder, make it the # first item in the list self.getActive().paste(self.cutbuffer, 1) elif self.getActive().isFolder(): # If we're pasting a non-folder into a folder, make it the # last sibling self.getActive().paste(self.cutbuffer, 2) else: # Otherwise, add the new item as the next sibling of whatever # was active. self.getActive().paste(self.cutbuffer, 3) return None def setActive(self, newactive): self.pos = newactive return self.pos def getActive(self): return self.pos def getTree(self): return self.root # soak up event argument when moused-over # could've used lambda but didn't... def mousefocus(self, event): self.focus_set() # open/close subtree def toggle(self, event=None): self.pos.toggle_state() # move to next lower visible node def next(self, event=None): self.move_cursor(self.pos.next()) # move to next higher visible node def prev(self, event=None): self.move_cursor(self.pos.prev()) # move to immediate parent def ascend(self, event=None): if self.pos.parent: # move to parent self.move_cursor(self.pos.parent) # move right, expanding as we go def descend(self, event=None): self.pos.toggle_state(1) if self.pos.subnodes: # move to first subnode self.move_cursor(self.pos.subnodes[0]) else: # if no subnodes, move to next sibling self.next() # go to root def first(self, event=None): # move to root node self.move_cursor(self.root) # go to last visible node def last(self, event=None): # move to bottom-most node n = self.root while n.subnodes: n = n.subnodes[-1] self.move_cursor(n) # previous page def pageup(self, event=None): n = self.pos j = self.winfo_height()/self.disty for i in range(j-3): n = n.prev() self.yview('scroll', -1, 'pages') self.move_cursor(n) # next page def pagedown(self, event=None): n = self.pos j = self.winfo_height()/self.disty for i in range(j-3): n = n.next() self.yview('scroll', 1, 'pages') self.move_cursor(n) # drag'n'drop support using Tkdnd # start drag'n'drop def dnd_click(self, event, node): Tkdnd.dnd_start(self, event) self.dnd_source = node # remember node we just entered, and save it as target def enter(self, node): self.dnd_target = node # we're not over a valid target def leave(self, event): self.dnd_target = None # end drag'n'drop def dnd_end(self, target, event): pass def dnd_accept(self, source, event): return self def dnd_commit(self, source, event): # destroy the move icon self.dnd_leave(None, None) # force update to get event, if any self.update() # see if non-trivial drag'n'drop occurred if self.dnd_target == None or source.dnd_source == self.dnd_target: return self.dnd_hook(source, self.dnd_target) # create drag icon def dnd_enter(self, source, event): # returns pointer position in display coordinates x, y = self.winfo_pointerxy() # translate to canvas coordinates x = self.canvasx(x)-self.winfo_rootx() y = self.canvasy(y)-self.winfo_rooty() i = source.itemcget(source.dnd_source.symbol, 'image') self.dnd_symbol = self.create_image(x, y, image=i) i = source.itemcget(source.dnd_source.label, 'text') self.dnd_label = self.create_text(x+self.textoff, y, text=i, justify='left', anchor='w' ) # destroy drag icon def dnd_leave(self, source, event): self.delete(self.dnd_symbol, self.dnd_label) # move drag icon def dnd_motion(self, source, event): # returns pointer position in display coordinates x, y = self.winfo_pointerxy() # translate to canvas coordinates x = self.canvasx(x)-self.winfo_rootx() y = self.canvasy(y)-self.winfo_rooty() self.coords(self.dnd_symbol, x, y) self.coords(self.dnd_label, x+self.textoff, y) forg-0.5.1.orig/default_bookmarks.xml0100644000175000017500000000236707344462645020106 0ustar jgoerzenjgoerzen Bookmarks Essential Links gopher.floodgap.com root gopher.quux.org root Development Projects Search gopherspace with Veronica 2 The Heatdeath Organization Whole Earth 'Lectronic Links forg-0.5.1.orig/forg.py0100755000175000017500000004557307344462645015210 0ustar jgoerzenjgoerzen#!/usr/bin/python # FORG.py # $Id: forg.py,v 1.15 2001/09/02 17:01:42 s2mdalle Exp $ # Written by David Allen # # This file contains the core FORG class, which handles most of the logic # and content. The actual GUI around it is handled by TkGui.py. # Theoretically, you could replace TkGui with GtkGui and it would work, but # not really, since you'd have to rewrite the Tkinter specific parts of # GUI*.py # # This object is meant as a wrapper for other things that subclass ContentFrame # This should simply be the object that is packed into the main GUI window # and be a subclass of Tkinter.Frame. # # This object knows how to download resources and display them properly by # using the other classes in the FORG. It basically is just a shell of a # Tkinter.Frame that contains the logic for using the right classes in the # right way. So without navigation, this contains all that is needed to # implement the functionality in other programs using just this class (and of # course the other things that it needs) # # 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. ############################################################################## # System wide imports from Tkinter import * # Tk interface from string import * # String manipulation from threading import * # Threads import Pmw # Python Mega Widgets import os # Operating system stuff import socket # Socket communication import sys import tkFileDialog # Non-GUI FORG specific imports from gopher import * import utils import GopherConnection import AskForm import GopherResource import GopherResponse import Cache import Options import Associations import List import ListNode import State # GUI Specific import GUIAskForm import GUIDirectory import GUIError import GUIFile import GUIQuestion import GUISaveFile import GUISearch import ResourceInformation import Dialogs #--------------------------- VERSION ------------------------------------ VERSION = '0.5.1' #--------------------------- VERSION ------------------------------------ FORGException = "Some error came up. I sure hope it came with a real message" FORGContentException = "Content was missing probably" def getVersion(): return VERSION class FORG(Frame): verbose = None def __init__(self, parent_widget, resource, response=None, messageBar=None, parent_object=None): Frame.__init__(self, parent_widget) self.parent_widget = parent_widget self.parent_object = parent_object self.response = response self.resource = resource self.mb = messageBar self.opts = Options.program_options # Global alias self.locations = List.List() self.navList = List.List() self._createPopup() self.currentContent = None # Go wherever the user intended us to go. self.goElsewhere(resource) return None def getCache(self): """Return the cache object being used""" return Options.program_options.getCache() def getMessageBar(self): """Return the message bar being used.""" return self.mb def setMessageBar(self, newmb): """Set the message bar object.""" self.mb = newmb return self.getMessageBar() def dumpQueue(self, *args): print "QUEUE IS:" def fn(node, s=self): r = node.getData().getResource() print r.toURL() self.navList.traverse(fn) def stats(self, *args): buffer = "%s item(s) in the queue\n" % self.navList.countNodes() buffer = buffer + Options.program_options.cache.getCacheStats() + "\n" if self.verbose: print "QUEUE IS:" def fn(node, s=self): r = node.getData().getResource() print r.toURL() self.navList.traverse(fn) self.genericMessage(buffer) def find(self, term, caseSensitive=None, lastIdentifier=None): """This find method forwards all arguments to the child widget and does no processing. See the documentation for ContentFrame.find for information on what parameters mean and what the method does.""" # Pass on to the child verbatim. return self.child.find(term, caseSensitive, lastIdentifier) def genericMessage(self, str, title='Information:'): """Given a string, pops up a dialog with the appropriate title and content string.""" x = Dialogs.InformationDialog(self, str, title) return None def genericError(self, errstr, title='Error:'): """Given an error string, pops up a dialog with the same title and text, alerting the user to something or other.""" x = Dialogs.ErrorDialog(self, errstr, title) return None def getResource(self): """Returns the stored GopherResource object that was used to create this object.""" return self.resource def getResponse(self): """Returns the stored GopherResponse object corresponding to the resource passed during the creation of this object""" return self.response def getGopherResponse(self): """This fetches the resource handed to the object from the network if necessary.""" self.conn = GopherConnection.GopherConnection() msg = "Done" try: self.response = self.conn.getResource(resource=self.resource, msgBar=self.mb) except GopherConnection.GopherConnectionException, estr: raise FORGException, estr except socket.error, err: try: if len(err) >= 2: msg = "Can't fetch: errcode %d: %s" % (err[0], err[1]) else: msg = "Can't fetch: error %s" % err[0] except AttributeError: # This is really weird, don't ask. msg = "Can't fetch - unknown error." err = "getGopherResponse: %s" % err raise FORGException, err if self.response.getError(): raise FORGException, self.response.getError() # Turn the green light back on, since we're done with the transmission. self.opts.greenLight() return None def popupMenu(self, event): """Display pop-up menu on right click on a message""" self.popup.tk_popup(event.x_root, event.y_root) def saveFile(self, *args): """Pop up a dialog box telling the user to choose a filename to save the file as.""" dir = os.path.abspath(os.getcwd()) filename = tkFileDialog.asksaveasfilename(initialdir=dir) listNode = self.navList.getCurrent() response = listNode.getData().getResponse() if filename and filename != "": try: response.writeToFile(filename) except IOError, errstr: self.genericError("Couldn't write data to\n%s:\n%s" % (filename, errstr)) return None def _createPopup(self): """Creates Pop-up menu on right click on a message""" self.popup = Menu(self) self.popup['tearoff'] = FALSE self.popup.add_command(label='Save', command=self.saveFile) self.popup.add_command(label='Back', command=self.goBackward) self.popup.add_command(label='Forward', command=self.goForward) def goForward(self, *rest): """Navigate forward""" try: node = self.navList.getNext() data = node.getData() # In case we are going forward to an error message... if not data.getResponse(): # Remove current item in the list. self.navList.removeCurrent() # Go to this location again. return self.goElsewhere(data.getResource()) self.createResponseWidget() self.changeContent(self.child) except List.ListException, errstr: if self.verbose: print "Couldn't get next: %s" % errstr return None def goBackward(self, *rest): """Navigate backward.""" try: node = self.navList.getPrev() state = node.getData() # In case we are going back to an error message... if not state.getResponse(): # Remove current item in the list. self.navList.removeCurrent() # Go to this location again. return self.goElsewhere(state.getResource()) self.response = state.getResponse() self.resource = state.getResource() self.createResponseWidget() node.setData(State.State(self.response, self.resource, self.child)) self.changeContent(self.child) except List.ListException, errstr: if self.verbose: print "Couldn't get prev: %s" % errstr def downloadResource(self, res): """Downloads the resource from the network and redisplays""" self.resource = res try: self.getGopherResponse() except FORGException, estr: self.genericError("Error fetching resource:\n%s" % estr) return None self.createResponseWidget() self.navList.insert(ListNode.ListNode(State.State(self.response, self.resource, self.child))) self.changeContent(self.child) return None def reload(self): newthread = Thread(group=None, target=self.reloadResource, name="Reload Thread", args=()) # Thread is responsible for adding the resource to the queue list # It will run and do its thing and when the function returns, the \ # thread ends. newthread.start() return None def reloadResource(self): """Reloads from the network the current resource.""" try: self.getGopherResponse() except FORGException, estr: self.genericError("Error fetching resource:\n%s" % estr) return None self.createResponseWidget() s = State.State(self.response, self.resource, self.child) self.navList.getCurrent().setData(s) self.changeContent(self.child) return None def getCurrentURL(self): return self.resource.toURL() def goElsewhere(self, resource, usecache=1, *args): """This is a wrapper for dealing with the download thread. Pass it one resource as an argument, and that resource will be downloaded in the background. Optionally, if the file exists in the cache, it will be loaded from there if usecache is true.""" self.resource = resource self.response = None optionsUseCache = Options.program_options.getOption("use_cache") # Attempt to load the response object out of the cache. if usecache and optionsUseCache and not resource.getDataBlock(): # Don't try to pull something out of cache unless it doesn't have # a data block. If it does, we have to submit information utils.msg(self.mb, "Looking for document in cache...") self.response = Options.program_options.cache.uncache(resource) if not self.response: utils.msg(self.mb, "Document not in cache. Fetching from network.") # We need to fetch this document from the network. # Signal the download thread to go to work, and get out. newthread = Thread(group=None, target=self.downloadResource, name="Download Thread", args=(self.resource,)) # Thread is responsible for adding the resource to the queue list newthread.start() return else: utils.msg(self.mb, "Loading document from cache to display.") self.createResponseWidget() s = State.State(self.response, self.resource, self.child) self.navList.insert(ListNode.ListNode(s)) self.changeContent(self.child) return None def changeContent(self, newwid): """Changes the current main content of the window to newwid""" if self.currentContent: self.currentContent.pack_forget() if newwid: utils.msg(self.mb, "Updating display...") newwid.pack(expand=1, fill='both') newwid.pack_content() utils.msg(self.mb, "Done") self.currentContent = newwid try: self.parent_object.change_content_hook(self.getResource()) except: # If it doesn't exist, or fails for some reason, that's the # parents problem, not ours. pass return self.currentContent def createResponseWidget(self): """Take the response already fetched and turn it into a child widget. This only works if the resource has already been fetched from the network.""" if not self.response: raise(FORGException, "createResponseWidget: No valid response present") if not self.resource: raise(FORGException, "createResponseWidget: No valid resource present") cfilename = None if self.opts.getOption('use_cache'): utils.msg(self.mb, 'Caching data...') cfilename = '' _resr = self.resource.shouldCache() _resp = self.response.shouldCache() if not self.resource.isAskType() and _resr and _resp: # Don't try to cache ASK blocks. It will only throw an # exception since caching them isn't a very stellar idea. try: cfilename = self.opts.cache.cache(resp=self.response, resource=self.resource) except Cache.CacheException, exceptionstr: self.genericError(exceptionstr) r = self.response if self.response.getTypeCode() == RESPONSE_INDEXS: self.child = GUISearch.GUISearch(parent_widget=self, parent_object=self, resp=self.response, resource=self.resource, filename=cfilename) elif self.response.__class__ == AskForm.AskForm: try: self.child = GUIAskForm.GUIAskForm(parent_widget=self, parent_object=self, resp=self.response, resource=self.resource, filename=cfilename) except Exception, errstr: print "******************************************************" print "Caught ", Exception, " with error ", errstr, " ASK" self.child = Label(self, text="Congrats! You've found a bug!") elif r.getData() != None and not r.looksLikeDir(r.getData()): if cfilename != None: assoc = self.opts.associations.getAssociation(cfilename) if assoc != None: self.LAUNCH_ME = [cfilename, assoc] if self.response.getTypeCode() == RESPONSE_FILE: self.child = GUIFile.GUIFile(parent_widget=self, parent_object=self, resp=self.response, resource=self.resource, filename=cfilename) else: self.child = GUISaveFile.GUISaveFile(parent_widget=self, parent_object=self, resp=self.response, resource=self.resource, filename=cfilename) else: # There is no data, display a directory entry ma = { "Back" : self.goBackward, "Forward" : self.goForward, "About FORG" : self.about } self.child = GUIDirectory.GUIDirectory(parent_widget=self, parent_object=self, resp=self.response, resource=self.resource, filename=cfilename, menuAssocs=ma) def about(self, *args): """Display the about box.""" Pmw.aboutversion(getVersion()) Pmw.aboutcopyright('Copyright 2000, 2001') Pmw.aboutcontact( 'This program is licensed under the GNU General Public License\n' + 'For more information, please see\n' + 'http://www.gnu.org/') self._about_dialog = Pmw.AboutDialog(self, applicationname='FORG') # self._about_dialog.show() return None ################################### MAIN CODE ############################### if __name__ == '__main__': # Require TkGui only if we are running the application stand-alone. from TkGui import * print "Starting the FORG" arr = sys.argv def main(item): try: app = TkGui(item) app.mainloop() except KeyboardInterrupt: app.quit() exit() url = "" try: url = arr[1] except: url = "gopher://gopher.floodgap.com:70/" if find(lower(url[:]), "gopher://") == -1: url = "gopher://" + url # Start the program print "Starting program with \"%s\"" % url main(url) forg-0.5.1.orig/gopher.py0100644000175000017500000001145407344462645015523 0ustar jgoerzenjgoerzen# gopher.py # $Id: gopher.py,v 1.7 2001/07/11 22:43:09 s2mdalle Exp $ # Gopher protocol definitions. # Written by David Allen # # 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. ######################################################################## RESPONSE_FILE = '0' # Item is a file RESPONSE_DIR = '1' # Item is a directory RESPONSE_CSO = '2' # Item is a CSO phone-book server RESPONSE_ERR = '3' # Error RESPONSE_BINHEX = '4' # Item is a BinHexed Macintosh file. RESPONSE_DOSBIN = '5' # Item is DOS binary archive of some sort. RESPONSE_UUE = '6' # Item is a UNIX uuencoded file. RESPONSE_INDEXS = '7' # Item is an Index-Search server. RESPONSE_TELNET = '8' # Item points to a text-based telnet session. RESPONSE_BINFILE = '9' # Item is a binary file! RESPONSE_REDSERV = '+' # Item is a redundant server RESPONSE_TN3270 = 'T' # Item points to a text-based tn3270 session. RESPONSE_GIF = 'g' # Item is a GIF format graphics file. RESPONSE_IMAGE = 'I' # Item is some kind of image file. RESPONSE_UNKNOWN = '?' # Unknown. WTF? RESPONSE_BITMAP = ':' # Gopher+ Bitmap response RESPONSE_MOVIE = ";" # Gopher+ Movie response RESPONSE_SOUND = "<" # Gopher+ Sound response # The following are types not found in the RFC definition of gopher but that # I've encountered on the net, so I added RESPONSE_BLURB = 'i' RESPONSE_HTML = 'h' # Gopher+ errors ERROR_NA = '1' # Item is not available. ERROR_TA = '2' # Try again later (eg. My load is too high right now.) ERROR_MOVED = '3' # Item has moved. responses = { "%s" % RESPONSE_FILE : "File:", "%s" % RESPONSE_DIR : "Directory:", "%s" % RESPONSE_CSO : "CSO phone-book server:", "%s" % RESPONSE_ERR : "Error:", "%s" % RESPONSE_BINHEX : "BinHexed Macintosh file:", "%s" % RESPONSE_DOSBIN : "DOS binary archive:", "%s" % RESPONSE_UUE : "UNIX UUEncoded file:", "%s" % RESPONSE_INDEXS : "Index-Search server:", "%s" % RESPONSE_TELNET : "Telnet session:", "%s" % RESPONSE_BINFILE : "Binary file:", "%s" % RESPONSE_REDSERV : "Redundant server:", "%s" % RESPONSE_TN3270 : "tn3270 session:", "%s" % RESPONSE_GIF : "GIF file:", "%s" % RESPONSE_IMAGE : "Image file:", "%s" % RESPONSE_BLURB : " ", "%s" % RESPONSE_HTML : "HTML file:", "%s" % RESPONSE_BITMAP : "Bitmap Image:", "%s" % RESPONSE_MOVIE : "Movie:", "%s" % RESPONSE_SOUND : "Sound:", "%s" % RESPONSE_UNKNOWN : "Unknown:" } errors = { "%s" % ERROR_NA : "Error: Item is not available.", "%s" % ERROR_TA : "Error: Try again (server busy)", "%s" % ERROR_MOVED : "Error: This resource has moved." } # There is nothing special about these numbers, just make sure they're # all unique. QUESTION_ASK = 20 QUESTION_ASKP = 21 QUESTION_ASKL = 22 QUESTION_ASKF = 23 QUESTION_SELECT = 24 QUESTION_CHOOSE = 25 QUESTION_CHOOSEF = 26 QUESTION_NOTE = 27 # Mapping from Gopher+ types to internal values. questions = { "Ask" : QUESTION_ASK, "AskP" : QUESTION_ASKP, "AskL" : QUESTION_ASKL, "AskF" : QUESTION_ASKF, "Select" : QUESTION_SELECT, "Choose" : QUESTION_CHOOSE, "ChooseF" : QUESTION_CHOOSEF, "Note" : QUESTION_NOTE } questions_types = { "%s" % QUESTION_ASK : "Ask", "%s" % QUESTION_ASKP : "AskP", "%s" % QUESTION_ASKL : "AskL", "%s" % QUESTION_ASKF : "AskF", "%s" % QUESTION_SELECT : "Select", "%s" % QUESTION_CHOOSE : "Choose", "%s" % QUESTION_CHOOSEF : "ChooseF" } # Colors - not related to gopher protocol functioning, but useful. RED = "#FF0000" GREEN = "#00FF00" BLUE = "#0000FF" WHITE = "#FFFFFF" BLACK = "#000000" forg-0.5.1.orig/mini-forg.py0100755000175000017500000000174607344462645016134 0ustar jgoerzenjgoerzen#!/usr/bin/python # Written by David Allen # This is a proof of concept on how to embed the application. Just using this # code embeds the entire application minus a few niceties (like bookmarks, etc) # into a popup window. # # This was just meant to display that the operation of the program is # segregated from the operation of the main GUI, and how to embed the FORG # in python/Tkinter programs. # # This file is released under the terms of the GNU General Public License. ############################################################################## from Tkinter import * import GopherResource import forg x = Tk() r = GopherResource.GopherResource() r.setURL("gopher://gopher.quux.org") r.setName("QUUX.org") # Create a FORG object. You only have to tell it what your parent # window is, and what resource it should load when it starts. f = forg.FORG(parent_widget=x, resource=r) # Pack it in f.pack(fill='both', expand=1) # Start the mini application. x.mainloop() forg-0.5.1.orig/utils.py0100644000175000017500000001110207344462645015365 0ustar jgoerzenjgoerzen# utils.py # Written by David Allen # $Id: utils.py,v 1.12 2001/07/05 17:16:53 s2mdalle Exp $ # Random functions used in many places that don't belong together in an # object. # # 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 string import * import os import stat def summarize_directory(dirname): """Returns an array [x, y, z] where x is the number of files in all subdirs of dirname and y is the total summed size of all of the files and z is the total number of directories.""" filecount = 0 summedSize = 0 dircount = 0 files = os.listdir(dirname) for file in files: if file == '.' or file == '..': continue path = os.path.join(dirname, file) if os.path.isdir(path): dircount = dircount + 1 # Discovered a new directory [tfc, tfs, tdc] = summarize_directory(path) # Add whatever came back from the recursive call. filecount = filecount + tfc summedSize = summedSize + tfs dircount = dircount + tdc else: filecount = filecount + 1 statinfo = os.stat(path) summedSize = summedSize + statinfo[stat.ST_SIZE] return [filecount, summedSize, dircount] def file_exists(filename): try: os.stat(filename) except OSError: return None return 1 def recursive_delete(dirname): """Runs the equivalent of an rm -rf on a given directory. Does not make any distinction between symlinks, etc. so use with extreme care. Thanks to comp.lang.python for tips on this one...""" files = os.listdir(dirname) for file in files: if file == '.' or file == '..': # The calls used to find filenames shouldn't ever return this, # but just in case, we check since this would be horrendously # bad. continue path = os.path.join(dirname, file) if os.path.isdir(path): recursive_delete(path) else: print 'Removing file: "%s"' % path retval = os.unlink(path) print 'Removing directory:', dirname os.rmdir(dirname) return 1 def character_replace(str, findchar, replacechar): if findchar is replacechar or findchar == replacechar: # That's a no-no... raise Exception, "character_replace: findchar == replacechar" ind = find(str, findchar) while ind != -1: str = str[0:ind] + "%s" % replacechar + str[ind+1:] ind = find(str, findchar) return str def dir_exists(dirname): try: stat_tuple = os.stat(dirname) except OSError: return None return stat.S_ISDIR(stat_tuple[0]) def make_directories(path, basedir): """Makes path directories off of basedir. Path is a relative path, and basedir is an absolute path. Example of invocation: make_directories('foo/bar/baz/quux', '/home/user/') will ensure that the path /home/user/foo/bar/baz/quux exists""" arr = split(path, os.sep) if basedir[len(basedir)-1] == os.sep: # Trim tailing dir separator basedir = basedir[0:len(basedir)-1] if not dir_exists(basedir): os.mkdir(basedir) for item in arr: if not item or item == '': continue dirname = "%s%s%s" % (basedir, os.sep, item) if not dir_exists(dirname): os.mkdir(dirname) basedir = dirname return 1 def msg(msgBar, msgtxt): """Puts msgtext into the msgBar. Does nothing if msgBar is None""" if msgBar != None: msgBar.message('state', msgtxt) #else: # print "=> msgbar: %s" % msgtxt def indent(indentlevel=1): str = "" if indentlevel < 0: raise Exception, "Indentlevel < 0 - you can't do that! :)" while indentlevel > 0: str = str + " " indentlevel = indentlevel - 1 return str forg-0.5.1.orig/xbel-1.0.dtd0100644000175000017500000000524707344462645015613 0ustar jgoerzenjgoerzen forg-0.5.1.orig/test/0040755000175000017500000000000007344462645014642 5ustar jgoerzenjgoerzenforg-0.5.1.orig/test/bmarks.xml0100644000175000017500000000151407344462645016641 0ustar jgoerzenjgoerzen Bookmarks Mothra Madness Essential Resources Floodgap Home Heatdeath Organization Quux.org UMN Home Veronica-2 Search forg-0.5.1.orig/test/README0100644000175000017500000000045707344462645015525 0ustar jgoerzenjgoerzenWhat you'll find here is several miscellaneous python scripts used to test different components of the system. When I first was writing things like List.py and the various socket routines, I needed tiny modules to test them and this is what I used. They aren't really of any value to the main program.forg-0.5.1.orig/test/bmetest.py0100644000175000017500000000017007344462645016652 0ustar jgoerzenjgoerzen#!/usr/bin/python from Tkinter import * import BookmarkEditor x = BookmarkEditor.BookmarkEditor('foobar') x.mainloop() forg-0.5.1.orig/test/bmtest.py0100644000175000017500000000105207344462645016505 0ustar jgoerzenjgoerzen from Tkinter import * import Bookmark f = Bookmark.BookmarkFactory() f.verbose = 1 f.parseResource(open("bmarks.xml")) win = Tk() m = Menu() mymenu = f.getMenu() def fn(item): print "You're going to \"%s\"-vill via way of %s" % (item.getName(), item.getURL()) return None m.add_cascade(label=mymenu.getName(), menu=mymenu.getTkMenu(m, fn)) print "Added cascade: %s" % mymenu.getName() win.config(menu=m) win.mainloop() f.writeXML(open("newbm.xml", "w"), f.getMenu()) forg-0.5.1.orig/test/ltest.py0100644000175000017500000000167107344462645016351 0ustar jgoerzenjgoerzen# Quick module to test the List and ListNode classes. from List import * from ListNode import * def p(x): print x.getData() def makenode(y): return ListNode("The number is: %d" % y) nodes = map(makenode, [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]) lst = List() for node in nodes: lst.insert(node) try: lst.getNext() lst.getNext() lst.getNext() lst.getNext() lst.getNext() except: pass try: lst.getPrev() lst.getPrev() lst.getPrev() except: pass lst.insert(ListNode("foo"), None) lst.insert(ListNode("bar"), None) lst.insert(ListNode("Baz"), None) lst.traverse(p) # Prints all numbers to 15 try: p(lst.getNext()) except: print "Can't get next item." print "===================" p(lst.getPrev()) p(lst.getPrev()) p(lst.getPrev()) p(lst.getPrev()) p(lst.getPrev()) p(lst.getPrev()) p(lst.getPrev()) p(lst.getPrev()) p(lst.getPrev()) p(lst.getPrev()) p(lst.getPrev()) p(lst.getPrev()) forg-0.5.1.orig/test/newbm.xml0100644000175000017500000000165007344462645016473 0ustar jgoerzenjgoerzen Bookmarks Mothra Madness Essential Resources Floodgap Home Heatdeath Organization Quux.org UMN Home Veronica-2 Search forg-0.5.1.orig/test/things.py0100644000175000017500000000034607344462645016510 0ustar jgoerzenjgoerzenimport GopherThingy import GopherObject import Bookmark import GopherResource import GopherResponse import GopherConnection import ErrorDialog import Associations import AssociationsEditor import ListNode import List import TkGui forg-0.5.1.orig/test/tri.py0100644000175000017500000000045007344462645016006 0ustar jgoerzenjgoerzenfrom GopherConnection import * from GopherResource import * from ResourceInformation import * res = GopherResource() res.setHost("mothra") res.setPort(70) res.setLocator("1/Source code") res.setName("Source code") conn = GopherConnection() resinfo = conn.getInfo(res) print resinfo.toString() forg-0.5.1.orig/Dialogs.py0100644000175000017500000001746107344462645015625 0ustar jgoerzenjgoerzen# Dialogs.py # $Id: Dialogs.py,v 1.5 2001/09/01 17:39:39 s2mdalle Exp $ # Written by David Allen # # Contains many different program dialogs used for information and data # entry purposes. # # 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 Tkinter import * from types import * from string import * import Pmw import GopherResource import Bookmark class FindDialog: def __init__(self, parent, searchwidget, parentobj=None, *args): self.parent = parent self.parentobj = parentobj self.searchwidget = searchwidget self.dialog = Pmw.Dialog(parent, title='Find Text...', buttons=('OK', 'Cancel'), defaultbutton='OK', command=self.dispatch) self.frame = Frame(self.dialog.interior()) self.frame.pack(expand=1, fill='both') Label(self.frame, text="Find a term...").grid(row=0, col=0, columnspan=5) Label(self.frame, text="Term: ").grid(row=1, col=0) self.searchEntry = Entry(self.frame, text="") self.searchEntry.grid(row=1, col=1, columnspan=4) self.css = IntVar() self.caseSensitiveCheckBox = Checkbutton(self.frame, text="Case-sensitive search", variable = self.css, command = self.cb) self.caseSensitiveCheckBox.grid(row=2, col=0, columnspan=4) self.lastMatch = None # self.dialog.activate() return None def cb(self, *args): # print "Var is ", self.css.get() return None def getSearchTerm(self): """Returns the search term currently in the search box.""" return self.searchEntry.get() def getCaseSensitive(self): """Returns the status of the case sensitive check button""" return self.css.get() def dispatch(self, button): """Handles button clicking in the dialog. (OK/Cancel)""" if button != 'OK': # Smack... self.dialog.destroy() return None # Otherwise, go look for a term... # try: self.lastMatch = self.searchwidget.find(self.getSearchTerm(), self.getCaseSensitive(), self.lastMatch) print "Last match is now ", self.lastMatch return self.lastMatch class OpenURLDialog: def __init__(self, parentwin, callback): self.callback = callback self.dialog = Pmw.Dialog(parentwin, title="Open URL:", command=self.dispatch, buttons=('OK', 'Cancel'), defaultbutton='OK') i = self.dialog.interior() Label(i, text="Enter URL to Open:").pack(side='top', expand=1, fill='both') self.urlEntry = Entry(i, width=30) self.urlEntry.insert('end', "gopher://") self.urlEntry.pack() def dispatch(self, button): if button == 'OK': # If OK is clicked, fire the callback with whatever the URL # happens to be. self.callback(self.urlEntry.get()) # In any case, destroy the dialog when finished. self.dialog.destroy() return None class NewBookmarkDialog: def __init__(self, parentwin, cmd, resource=None): self.cmd = cmd self.dialog = Pmw.Dialog(parentwin, title="New Bookmark:", command=self.callback, buttons=('OK', 'Cancel')) i = self.dialog.interior() namebox = Frame(i) urlbox = Frame(i) Label(i, text="Enter Bookmark Information:").pack(side='top', expand=1, fill='both') namebox.pack(fill='both', expand=1) urlbox.pack(fill='both', expand=1) Label(namebox, text="Name:").pack(side='left') self.nameEntry = Entry(namebox, width=30) self.nameEntry.pack(side='right', fill='x', expand=1) Label(urlbox, text="URL:").pack(side='left') self.URLEntry = Entry(urlbox, width=30) self.URLEntry.pack(side='right', fill='x', expand=1) if resource: self.URLEntry.insert('end', resource.toURL()) self.nameEntry.insert('end', resource.getName()) return None def callback(self, button): if button and button != 'Cancel': res = Bookmark.Bookmark() res.setURL(self.URLEntry.get()) res.setName(self.nameEntry.get()) self.cmd(res) # Do whatever our parent wants us to with this... self.dialog.destroy() return None class NewFolderDialog: BUTTON_OK = 0 BUTTON_CANCEL = 1 def __init__(self, parentwin, callback, folderName=None): self.buttons = ('OK', 'Cancel') self.callback = callback self.parent = parentwin self.dialog = Pmw.Dialog(parentwin, title="New Folder:", command=self.closeDialog, buttons=self.buttons) i = self.dialog.interior() Label(i, text="New Folder Title:").grid(row=0, col=0, sticky='EW') self.__entry = Entry(i) self.__entry.grid(row=1, col=0, sticky='EW') if folderName: self.__entry.insert('end', folderName) self.bindCallbacks() return None def bindCallbacks(self): self.__entry.bind('', self.closeDialog) return None def closeDialog(self, result): if result == self.buttons[self.BUTTON_CANCEL]: self.dialog.destroy() return None else: str = self.__entry.get() self.dialog.destroy() return self.callback(str) # End NewFolderDialog class InformationDialog: def __init__(self, parent, errstr, title='Information:'): # We don't need an activate command since we want the dialog to just # get smacked when the user presses close. if title == '': title = errstr self.dialog = Pmw.Dialog(parent, title=title, buttons=["Close"]) # print "========================================================" # print "Error Dialog: %s" % errstr # print "========================================================" if type(errstr) != StringType: errstr = errstr.__str__ labels = split(errstr, "\n") for label in labels: Label(self.dialog.interior(), text=label).pack(side='top') # self.dialog.activate() # Modalize :) return None class ErrorDialog(InformationDialog): def __init__(self, parent, errstr, title="Error:"): InformationDialog.__init__(self, parent, errstr, title) return None forg-0.5.1.orig/default_options0100644000175000017500000000115307344462645017002 0ustar jgoerzenjgoerzen# This is a FORG configuration file. # From here, you can configure which fonts and colors the FORG uses. # # I strongly recommend a fixed-width font, particularly since on many # gopher sites, you'll find ASCII art that doesn't display well with # variable width fonts. # # In order to make this options file "active", copy it to $HOME/.forg/options # and that's all there is to it. # ############################################################################## *font: -adobe-courier-*-r-*-*-12-*-*-*-*-*-*-* *Label*font: -adobe-courier-*-r-*-*-12-*-*-*-*-*-*-* *background: Gray80 *Entry*background: white