[LWN Logo]

Date: Mon, 02 Nov 1998 22:59:24 -0700
From: Andrew Dalke <dalke@bioreason.com>
Subject: ANN: CrossCopy

Below is my first real Python program (from about 6 months ago).
It mostly solved a frustration that I no longer have.

The problem:
  Our bug database was on an NT box but our code ran on SGIs.
If there was a bug in the code and we wanted to copy&paste
text into the database entry form we would have to save to a file
on the SGI, (re)load the file on the NT then copy&paste
*that* text into the form.
  Needless to say, this got boring fast.

One solution:
  At this time I was reading the Python documentation and
itching to write some real code.  What I wrote was a simple
Tk GUI that ran an HTTP server.  The GUI contained a list
of servers and when a server was selected would connect
to it an display the text, which could be cut&pasted (saving
the manual "write to a temporary file" step).

  The HTTP server would listen for requests and serve the
text in its text box.

Another solution:
  Get another job where everything can be done on the same
machine (the joys of a web interface to the bug database :)


An implementation to the first solution is below.  After some
thought I decided to name is "CrossCopy."  AltaVista comes up
with no matching names, which I found surprising.

There's all sorts of things that could be done:
  read configuration from a file
  allow users to edit the configuration
  add a "Clear" button
  pull the text directly from the cut buffer or moral equivalent
  recognize that you cannot connect to yourself (or use multiple
     threads) -- at present this causes the system to freeze.

Since I no longer have the problem, I spent no more time in
development.  However, I figured others here might find such
a program useful and even be willing to work on it (that's what
ESR says, right? :) so, presented for your approval, the
source.  The copyright is the same as Python's, but with
explicit perrmision to distribute modified versions.

						Andrew Dalke
						dalke@bioreason.com

#!/usr/local/bin/python

# Copyright 1998 by Andrew Dalke  -- All Rights Reserved
# 
# Permission to use, copy, modify, and distribute this software
# (including modifications) and its documentation for any purpose and
# without fee is hereby granted, provided that the above copyright
# notice appear in all copies and that both that copyright notice and
# this permission notice appear in supporting documentation.
#
# ANDREW DALKE DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
# INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN
# NO EVENT SHALL ANDREW DALKE OR BIOREASIN, INC.  BE LIABLE FOR ANY
# SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
# RESULTING FROM LOSS OF USE, DATA OR PROFITS,WHETHER IN AN ACTION OF
# CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.


import BaseHTTPServer, urllib
import sys, os, select, getopt, socket
from Tkinter import *

class CrossCopyHandler(BaseHTTPServer.BaseHTTPRequestHandler):
    def do_GET(self):
        self.end_headers()
        self.wfile.write(server_text())

class RemoteInfo:
    def __init__(self, url, title = '', username = '',
                 password = ''):
        if title == '':            # human text (defaults to url)
            self.title = url
        else:
            self.title = title
        self.url = url             # where is the server?
        self.username = username   # not used
        self.password = password   # not used
    def goto(self):
        get_text(self.url)


###################### configuration information ################
# You'll want to modify this for your system.
# Should read from file ...
remote = []
remote.append(RemoteInfo('http://bioreason3:8000/'))
remote.append(RemoteInfo('http://bioreason8:8000/'))
remote.append(RemoteInfo('http://bioreason9:8000/'))

#### Get the text to pass back to the client (from the server)
def server_text():
    global ccserver
    global ccclient
    global root
    if ccclient is not None:
        return ccclient.text.get("0.0", "end")
    return root.selection_get()


####### Client GUI Code

class CrossCopyClient:
    def __init__(self, root):
        # make a menu bar
        self.mBar = Frame(root, relief=RAISED, borderwidth=2)
        self.mBar.pack(fill=X)

        self.Command_button = self.makeCommandMenu(self.mBar)
        self.Edit_button = self.makeEditMenu(self.mBar) ## no remote
        self.mBar.tk_menuBar(self.Command_button)
        self.text = Text(root)
        self.text.pack()


    # menu bars

    def makeCommandMenu(self, mBar):
        Command_button = Menubutton(mBar, text='File', 
                                    underline=0)
        Command_button.pack(side=LEFT, padx="2m")
        Command_button.menu = Menu(Command_button)
        
        Command_button.menu.add_command(label='Quit', underline=0, 
                                        command=quit)
        
        # set up a pointer from the file menubutton back to the file menu
        Command_button['menu'] = Command_button.menu
                                        
        return Command_button

    def makeEditMenu(self, mBar):
        global remote
        Edit_button = Menubutton(mBar, text='Edit',
                                 underline = 0)
        Edit_button.pack(side=LEFT, padx="2m")
        Edit_button.menu = Menu(Edit_button)
        for r in remote:
            Edit_button.menu.add_command(label= r.title, command=r.goto)
            
        Edit_button['menu'] = Edit_button.menu
        return Edit_button
        
###### Server code
class CrossCopyServer:
    def __init__(self, port):
        # Start the web server
        self.server_class = BaseHTTPServer.HTTPServer
        self.handler_class = CrossCopyHandler
        self.server_address = ('', port)
        self.httpd = self.server_class(self.server_address, self.handler_class)

    def fileno(self):
        return self.httpd.fileno()

    def handle_request(self):
        return self.httpd.handle_request()

def get_text(url):
    a = urllib.urlopen(url)
    ccclient.text.delete('0.0', 'end')
    ccclient.text.insert('end', a.read())

def quit():
    root.quit()

def check_ccserver():
    global ccserver
    global root
    if select.select([ccserver], [], [], 0) == ([ccserver], [], []):
        ccserver.handle_request()
    root.after(200, check_ccserver)

if __name__ == '__main__':
    server = 1
    client = 1
    port = 8000
    initfile = '/u/dalke/.crosscopyrc' # not the we use it ... yet
    args = sys.argv[:]
    try:
        opts, rest = getopt.getopt(sys.argv[1:], '',
                                   ['client=', 'server=', 'port=', 'file='])

        if len(rest) > 2: raise getopt.error, 'too many arguments'

        for option, optarg in opts:
            if option == '--server':
                server = int(optarg)
            elif option == '--client':
                client = int(optarg)
            elif option == '--port':
                port = int(optarg)
            elif option == '--file':
                initfile = optarg

    except getopt.error, msg:
        print msg
        print 'Usage: ',
        print sys.argv[0],
        print ' [--server [0|1] ] [--client [0|1]] [--port <number>]'

    # should read configuration file here


    root = Tk()
    # unix specific so ignore for now (should check environ)
    # hostname = os.popen('hostname', 'r').read()
    hostname = 'CrossCopy'

    root.title('crosscopy on ' + hostname)
    if client != 0:
        # start the client
        ccclient = CrossCopyClient(root)
    else:
        ccclient = None
        # still need it to get the current paste selection
        # but don't want it to appear
        root.withdraw()

    if server != 0:
        # start the web server
        try:
            ccserver = CrossCopyServer(port)
        except socket.error, msg:
            print "Cannot bind to port %s: %s" % (port, msg)

    # whatever ...
    if client == 0 and server == 0:
        sys.exit()

    # must periodically check the server, if we have one
    if server != 0:
        root.after(200, check_ccserver)
    root.mainloop()