aprs.py
  1. # -*- coding: UTF-8 -*-
  2. # Copyright (C) 2008, Jack Zielke <jack@linuxcoffee.com>
  3. # Copyright (C) 2008, One Laptop Per Child
  4. #
  5. # This program is free software; you can redistribute it and/or modify
  6. # it under the terms of the GNU General Public License as published by
  7. # the Free Software Foundation; either version 2 of the License, or
  8. # (at your option) any later version.
  9. #
  10. # This program is distributed in the hope that it will be useful,
  11. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  13. # GNU General Public License for more details.
  14. #
  15. # You should have received a copy of the GNU General Public License
  16. # along with this program; if not, write to the Free Software
  17. # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
  18.  
  19. import gtk
  20. import pango
  21. import time
  22. import socket
  23. import random
  24. import gobject
  25.  
  26. from gettext import gettext as _
  27.  
  28. from sugar import profile
  29. from sugar.activity import activity
  30. from sugar.bundle.activitybundle import ActivityBundle
  31. from sugar.graphics.menuitem import MenuItem
  32. from sugar.graphics.toolbutton import ToolButton
  33.  
  34. TESTING = False
  35.  
  36. HOST = 'rotate.aprs2.net'
  37. #HOST = '192.168.50.6'
  38. PORT = 14580
  39. RECV_BUFFER = 4096
  40.  
  41. MAXLINES = 50
  42. MAXRETRIES = 15
  43. MAX_MSG_QUEUE = 10
  44. MAX_PER_CALL_QUEUE = 2
  45.  
  46. bundle = ActivityBundle(activity.get_bundle_path())
  47. VERSION = bundle.get_activity_version()
  48. del bundle
  49.  
  50. class APRSActivity(activity.Activity):
  51.     def __init__(self, handle):
  52.         activity.Activity.__init__(self, handle)
  53.         self.set_title(_('APRS-XO Activity'))
  54.  
  55.         self.sock = None
  56.         self.location = "home"
  57.         self.site = None
  58.         self.sent_acks = {}
  59.         self.recv_acks = {}
  60.         self.timers = []
  61.         self.bulletins = ["AIR", "DGPS", "QST", "TEL", "ALL", "DRILL", "QTH", "TEST", "AP", "DX", "RTCM", "TLM", "BEACON", "ID", "SKY", "WX", "CQ", "JAVA", "SPACE", "ZIP", "GPS", "MAIL", "SPC", "DF", "MICE", "SYM", "BLN", "NWS", "NTS"]
  62.         self.validating = False
  63.         self.help = True
  64.         self.messagebox = False
  65.         self.sequence = random.randrange(0, 7000)
  66.         self.queue_list = {}
  67.         self.message_list = {}
  68.         self.seen_bulletins = {}
  69.         self.message_marks = {}
  70.  
  71.         titlefont = pango.FontDescription('Sans bold 8')
  72.         mediumfont = pango.FontDescription('Sans 6.5')
  73.         smallfont = pango.FontDescription('Sans 6')
  74.         verysmallfont = pango.FontDescription('Sans 4')
  75.         firstName = profile.get_nick_name().split(None, 1)[0].capitalize()
  76.  
  77.         toolbox = activity.ActivityToolbox(self)
  78.         self.set_toolbox(toolbox)
  79.  
  80.         activity_toolbar = toolbox.get_activity_toolbar()
  81.         activity_toolbar.share.props.visible = False
  82.         activity_toolbar.keep.props.visible = False
  83.  
  84.         toolbox.show()
  85.  
  86.         win = gtk.HBox(False, 10)
  87.         self.set_canvas(win)
  88.  
  89.         leftwin = gtk.VBox(False, 10)
  90.  
  91.         # Top 'about' box
  92.         aboutbox = gtk.VBox(False, 11)
  93.         aboutbox.set_border_width(10)
  94.  
  95.         topaboutbox = gtk.VBox(False, 0)
  96.  
  97.         titlebox = gtk.HBox(False, 0)
  98.         titlelabel = gtk.Label("APRS-XO:")
  99.         titlelabel.set_alignment(0, 0)
  100.         titlelabel.modify_font(titlefont)
  101.         titlebox.pack_start(titlelabel, False, False, 0)
  102.         titlelabel.show()
  103.         aboutlabel1 = gtk.Label("This amateur radio program will update your")
  104.         aboutlabel1.set_alignment(0, 0.8)
  105. #        aboutlabel1.modify_font(mediumfont)
  106.         titlebox.pack_start(aboutlabel1, False, False, 0)
  107.         aboutlabel1.show()
  108.         topaboutbox.pack_start(titlebox, False, False, 0)
  109.         titlebox.show()
  110.         aboutlabel2 = gtk.Label("positon & status on all of the global APRS web pages once\nevery 10 minutes.")
  111.         aboutlabel2.set_alignment(0, 0)
  112. #        aboutlabel2.modify_font(mediumfont)
  113.         topaboutbox.pack_start(aboutlabel2, False, False, 0)
  114.         aboutlabel2.show()
  115.         aboutbox.pack_start(topaboutbox, False, False, 0)
  116.         topaboutbox.show()
  117.  
  118.         sitebox = gtk.HBox(False, 10)
  119.         sitelabel = gtk.Label("Select an APRS site:")
  120.         sitelabel.set_alignment(0, 0.4)
  121.         sitebox.pack_start(sitelabel, False, False, 0)
  122.         sitelabel.show()
  123.  
  124.         findubutton = gtk.Button()
  125.         findubutton.set_label("FINDU.COM")
  126.         findubutton.connect("clicked", self.set_site, "http://www.findu.com/cgi-bin/symbol.cgi?icon=XA&limit=200")
  127.         sitebox.pack_start(findubutton, False, False, 0)
  128.         findubutton.show()
  129.  
  130.         aprsworldbutton = gtk.Button()
  131.         aprsworldbutton.set_label("APRSworld")
  132.         aprsworldbutton.connect("clicked", self.set_site, "http://aprsworld.net/")
  133.         sitebox.pack_start(aprsworldbutton, False, False, 0)
  134.         aprsworldbutton.show()
  135.  
  136.         otherbutton = gtk.Button()
  137.         otherbutton.set_label("About")
  138.         otherbutton.connect("clicked", self.set_site, "http://aprs.org/")
  139.         sitebox.pack_start(otherbutton, False, False, 0)
  140.         otherbutton.show()
  141.  
  142.         aboutbox.pack_start(sitebox, False, False, 0)
  143.         sitebox.show()
  144.  
  145. #        infobox = gtk.VBox(False, 4)
  146.  
  147. ##        infolabel = gtk.Label("With a ham license you can communicate worldwide with wireless and radio.\nWithout a ham license you can only communicate with other XO's on the\nInternet.")
  148. #        infolabel = gtk.Label("With a ham license you can communicate worldwide with wireless and radio.")
  149. #        infolabel.set_alignment(0, 0)
  150. #        infolabel.modify_font(smallfont)
  151. #        infobox.pack_start(infolabel, False, False, 0)
  152. #        infolabel.show()
  153.  
  154. #        aboutbox.pack_start(infobox, False, False, 0)
  155. #        infobox.show()
  156.  
  157.         leftwin.pack_start(aboutbox, False, False, 0)
  158.         aboutbox.show()
  159.  
  160.         separator = gtk.HSeparator()
  161.         leftwin.pack_start(separator, False, False, 0)
  162.         separator.show()
  163.  
  164.         # identifiers box
  165.         identbox = gtk.VBox(False, 4)
  166.         identbox.set_border_width(10)
  167.  
  168.         identlabel = gtk.Label("Identifiers")
  169.         identlabel.set_alignment(0, 0)
  170.         identlabel.modify_font(titlefont)
  171.         identbox.pack_start(identlabel, False, False, 0)
  172.         identlabel.show()
  173.  
  174.         bottomidentbox = gtk.HBox(False, 10)
  175.  
  176.         calllabel1 = gtk.Label("Callsign: ")
  177.         calllabel1.set_alignment(1, 0.5)
  178.         bottomidentbox.pack_start(calllabel1, False, False, 0)
  179.         calllabel1.show()
  180.  
  181.         self.calltext = gtk.Entry()
  182.         self.calltext.set_max_length(9)
  183.         self.calltext.set_width_chars(9)
  184.         self.calltext.connect("changed", self.disable_beacon)
  185.         bottomidentbox.pack_start(self.calltext, False, False, 0)
  186.         self.calltext.show()
  187.  
  188.         passlabel1 = gtk.Label("Password: ")
  189.         passlabel1.set_alignment(1, 0.5)
  190.         bottomidentbox.pack_start(passlabel1, False, False, 0)
  191.         passlabel1.show()
  192.  
  193.         self.passtext = gtk.Entry()
  194.         self.passtext.set_max_length(5)
  195.         self.passtext.set_width_chars(5)
  196.         self.passtext.set_invisible_char("x")
  197.         self.passtext.set_visibility(False)
  198.         bottomidentbox.pack_start(self.passtext, False, False, 0)
  199.         self.passtext.show()
  200.  
  201.         identbox.pack_start(bottomidentbox, False, False, 0)
  202.         bottomidentbox.show()
  203.  
  204.         passlabel2 = gtk.Label("optional")
  205.         passlabel2.set_alignment(0.74, 0)
  206.         passlabel2.modify_font(smallfont)
  207.         identbox.pack_start(passlabel2, False, False, 0)
  208.         passlabel2.show()
  209.  
  210.         leftwin.pack_start(identbox, False, False, 0)
  211.         identbox.show()
  212.  
  213.         separator = gtk.HSeparator()
  214.         leftwin.pack_start(separator, False, False, 0)
  215.         separator.show()
  216.  
  217.         # station box
  218.         stationbox = gtk.VBox(False, 11)
  219.         stationbox.set_border_width(10)
  220.  
  221.         stationlabel = gtk.Label("Station Comment")
  222.         stationlabel.set_alignment(0, 0)
  223.         stationlabel.modify_font(titlefont)
  224.         stationbox.pack_start(stationlabel, False, False, 0)
  225.         stationlabel.show()
  226.  
  227.         # so the text box does not fill all Horizontal space
  228.         stationtextbox = gtk.HBox()
  229.  
  230.         self.stationtext = gtk.Entry()
  231.         self.stationtext.set_max_length(43)
  232.         self.stationtext.set_width_chars(43)
  233.         self.stationtext.set_text("%s's XO at home." % firstName)
  234.         self.stationtext.connect("changed", self.disable_beacon)
  235.         stationtextbox.pack_start(self.stationtext, False, False, 0)
  236.         self.stationtext.show()
  237.         stationbox.pack_start(stationtextbox, False, False, 0)
  238.         stationtextbox.show()
  239.  
  240.         stationhelp = gtk.Label("Optional description of current position, status,\nor destination.  Enter up to 43 characters, most\nimportant first since some displays can only see\nthe first 20 or 28.")
  241.         stationhelp.set_alignment(0, 0)
  242. #        stationhelp.modify_font(mediumfont)
  243.         stationbox.pack_start(stationhelp, False, False, 0)
  244.         stationhelp.show()
  245.  
  246.         leftwin.pack_start(stationbox, False, False, 0)
  247.         stationbox.show()
  248.  
  249.         separator = gtk.HSeparator()
  250.         leftwin.pack_start(separator, False, False, 0)
  251.         separator.show()
  252.  
  253.         # position box
  254.         positbox = gtk.VBox(False, 11)
  255.         positbox.set_border_width(10)
  256.  
  257.         toppositbox = gtk.HBox(False, 0)
  258.  
  259.         topleftpositbox = gtk.VBox(False, 4)
  260.  
  261.         positlabel1 = gtk.Label("Position Data")
  262.         positlabel1.set_alignment(0, 0)
  263.         positlabel1.modify_font(titlefont)
  264.         topleftpositbox.pack_start(positlabel1, False, False, 0)
  265.         positlabel1.show()
  266.  
  267.         latpositbox = gtk.HBox(False, 4)
  268.         latlabel1 = gtk.Label("Latitude:    ")
  269.         latlabel1.set_alignment(0, 0.5)
  270.         latpositbox.pack_start(latlabel1, False, False, 0)
  271.         latlabel1.show()
  272.  
  273.         self.latDDtext = gtk.Entry()
  274.         self.latDDtext.set_max_length(2)
  275.         self.latDDtext.set_width_chars(4)
  276.         self.latDDtext.set_text("DD")
  277.         self.latDDtext.connect("changed", self.disable_beacon)
  278.         latpositbox.pack_start(self.latDDtext, False, False, 0)
  279.         self.latDDtext.show()
  280.  
  281.         self.latMMtext = gtk.Entry()
  282.         self.latMMtext.set_max_length(2)
  283.         self.latMMtext.set_width_chars(3)
  284.         self.latMMtext.set_text("MM")
  285.         self.latMMtext.connect("changed", self.disable_beacon)
  286.         latpositbox.pack_start(self.latMMtext, False, False, 0)
  287.         self.latMMtext.show()
  288.  
  289.         latlabel2 = gtk.Label(".")
  290.         latlabel2.set_alignment(0, 1)
  291.         latpositbox.pack_start(latlabel2, False, False, 0)
  292.         latlabel2.show()
  293.  
  294.         self.latmmtext = gtk.Entry()
  295.         self.latmmtext.set_max_length(2)
  296.         self.latmmtext.set_width_chars(3)
  297.         self.latmmtext.set_text("mm")
  298.         self.latmmtext.connect("changed", self.disable_beacon)
  299.         latpositbox.pack_start(self.latmmtext, False, False, 0)
  300.         self.latmmtext.show()
  301.  
  302.         self.latcombo = gtk.combo_box_new_text()
  303.         self.latcombo.append_text("N")
  304.         self.latcombo.append_text("S")
  305.         self.latcombo.set_active(0)
  306.         latpositbox.pack_start(self.latcombo, False, False, 0)
  307.         self.latcombo.show()
  308.  
  309.         topleftpositbox.pack_start(latpositbox, False, False, 0)
  310.         latpositbox.show()
  311.  
  312.         lonpositbox = gtk.HBox(False, 4)
  313.         lonlabel1 = gtk.Label("Longitude: ")
  314.         lonlabel1.set_alignment(0, 0.5)
  315.         lonpositbox.pack_start(lonlabel1, False, False, 0)
  316.         lonlabel1.show()
  317.  
  318.         self.lonDDDtext = gtk.Entry()
  319.         self.lonDDDtext.set_max_length(3)
  320.         self.lonDDDtext.set_width_chars(4)
  321.         self.lonDDDtext.set_text("DDD")
  322.         self.lonDDDtext.connect("changed", self.disable_beacon)
  323.         lonpositbox.pack_start(self.lonDDDtext, False, False, 0)
  324.         self.lonDDDtext.show()
  325.  
  326.         self.lonMMtext = gtk.Entry()
  327.         self.lonMMtext.set_max_length(2)
  328.         self.lonMMtext.set_width_chars(3)
  329.         self.lonMMtext.set_text("MM")
  330.         self.lonMMtext.connect("changed", self.disable_beacon)
  331.         lonpositbox.pack_start(self.lonMMtext, False, False, 0)
  332.         self.lonMMtext.show()
  333.  
  334.         lonlabel2 = gtk.Label(".")
  335.         lonlabel2.set_alignment(0, 1)
  336.         lonpositbox.pack_start(lonlabel2, False, False, 0)
  337.         lonlabel2.show()
  338.  
  339.         self.lonmmtext = gtk.Entry()
  340.         self.lonmmtext.set_max_length(2)
  341.         self.lonmmtext.set_width_chars(3)
  342.         self.lonmmtext.set_text("mm")
  343.         self.lonmmtext.connect("changed", self.disable_beacon)
  344.         lonpositbox.pack_start(self.lonmmtext, False, False, 0)
  345.         self.lonmmtext.show()
  346.  
  347.         self.loncombo = gtk.combo_box_new_text()
  348.         self.loncombo.append_text("W")
  349.         self.loncombo.append_text("E")
  350.         self.loncombo.set_active(0)
  351.         lonpositbox.pack_start(self.loncombo, False, False, 0)
  352.         self.loncombo.show()
  353.  
  354.         topleftpositbox.pack_start(lonpositbox, False, False, 0)
  355.         lonpositbox.show()
  356.  
  357.         toppositbox.pack_start(topleftpositbox, False, False, 0)
  358.         topleftpositbox.show()
  359.  
  360.         toprightpositbox = gtk.VBox(False, 4)
  361.         toprightpositbox.set_border_width(10)
  362.  
  363.         loclabel = gtk.Label("-OR- Zip Code:")
  364.         loclabel.set_alignment(0.3, 0)
  365.         toprightpositbox.pack_start(loclabel, False, False, 0)
  366.         loclabel.show()
  367.  
  368.         self.ziptext = gtk.Entry()
  369.         self.ziptext.set_max_length(5)
  370.         self.ziptext.set_width_chars(5)
  371.         self.ziptext.connect("changed", self.disable_beacon)
  372.         toprightpositbox.pack_start(self.ziptext, False, False, 0)
  373.         self.ziptext.show()
  374.  
  375.         toppositbox.pack_start(toprightpositbox, False, False, 0)
  376.         toprightpositbox.show()
  377.  
  378.         positbox.pack_start(toppositbox, False, False, 0)
  379.         toppositbox.show()
  380.  
  381.         positlabel2 = gtk.Label("If you do not know your LAT/LONG then your zip code will\nbe used to place you on the map.")
  382.         positlabel2.set_alignment(0, 0)
  383. #        positlabel2.modify_font(mediumfont)
  384.         positbox.pack_start(positlabel2, False, False, 0)
  385.         positlabel2.show()
  386.  
  387.         leftwin.pack_start(positbox, False, False, 0)
  388.         positbox.show()
  389.  
  390.         separator = gtk.HSeparator()
  391.         leftwin.pack_start(separator, False, False, 0)
  392.         separator.show()
  393.  
  394.         # defined here so clear and connect buttons have access
  395.         self.statusview = gtk.TextView()
  396.         self.statusbuffer = self.statusview.get_buffer()
  397.         self.messageview = gtk.TextView()
  398.         self.messagebuffer = self.messageview.get_buffer()
  399.  
  400.         connectbutton = gtk.Button()
  401.         connectbutton.set_label("Connect")
  402.         connectbutton.modify_bg(gtk.STATE_NORMAL, gtk.gdk.color_parse("#00b20d"))
  403.         connectbutton.connect("clicked", self.connect_aprs)
  404.         leftwin.pack_start(connectbutton, False, False, 12)
  405.         connectbutton.show()
  406.  
  407.         win.pack_start(leftwin, False, False, 0)
  408.         leftwin.show()
  409.  
  410.         rightwin = gtk.VBox(False, 4)
  411.         rightwin.set_border_width(4)
  412.  
  413.         rightwintopbox = gtk.HBox(False, 4)
  414.  
  415.         clearbutton = gtk.Button()
  416.         clearbutton.set_label("  Clear  ")
  417.         clearbutton.connect("clicked", self.clear_message)
  418.         rightwintopbox.pack_start(clearbutton, False, False, 20)
  419.         clearbutton.show()
  420.  
  421.         self.beaconbutton = gtk.CheckButton("Beacon every 10 minutes")
  422.         self.beaconbutton.set_active(True)
  423.         self.beaconbutton.connect("toggled", self.enable_beacon, "beacon")
  424.         rightwintopbox.pack_start(self.beaconbutton, False, False, 20)
  425.         self.beaconbutton.show()
  426.  
  427.         rightwin.pack_start(rightwintopbox, False, False, 0)
  428.         rightwintopbox.show()
  429.  
  430.         self.messageview.set_editable(False)
  431.         self.messageview.set_cursor_visible(True)
  432.         self.messageview.set_wrap_mode(gtk.WRAP_CHAR)
  433.         self.messageview.set_justification(gtk.JUSTIFY_LEFT)
  434.         self.messageview.modify_font(smallfont)
  435.  
  436.         self.messagebuffer.set_text("Welcome to APRS-XO.\n\nThis program sends your position information to a server that will\ndisplay your location on a webpage.  This program requires an\nactive Internet connection to work.\n\nSelect an APRS Site\nSelecting a button will copy the URI to the clipboard.\n\nIndentifiers\nEnter your callsign and optionally an aprsd password.\n\nStation Comment\nData in the Station Comment field will appear after your location\ninformation on the website.")
  437.  
  438.         self.messagewindow = gtk.ScrolledWindow()
  439.         self.messagewindow.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_ALWAYS)
  440.         self.messagewindow.add_with_viewport(self.messageview)
  441.         self.messageview.show()
  442.         rightwin.pack_start(self.messagewindow, True, True, 0)
  443.         self.messagewindow.show()
  444.  
  445.         messagebox = gtk.HBox(False, 4)
  446.  
  447.         self.messagecombo = gtk.combo_box_entry_new_text()
  448.         self.messagecombo.append_text("ALL")
  449.         self.messagecombo.append_text("BEACON")
  450.         self.messagecombo.append_text("CQ")
  451.         self.messagecombo.append_text("QST")
  452.         self.messagecombo.set_active(-1)
  453.         self.messagedest = self.messagecombo.get_child()
  454.         self.messagedest.set_max_length(9)
  455.         self.messagedest.set_width_chars(5)
  456.         self.messagedest.modify_font(smallfont)
  457.         messagebox.pack_start(self.messagecombo, False, False, 0)
  458.         self.messagecombo.show()
  459.  
  460.         self.messagetext = gtk.Entry()
  461.         self.messagetext.set_max_length(67)
  462.         self.messagetext.set_width_chars(32)
  463.         self.messagetext.modify_font(smallfont)
  464.         self.messagetext.connect("activate", self.send_message, self.messagetext)
  465.         messagebox.pack_start(self.messagetext, False, False, 0)
  466.         self.messagetext.show()
  467.  
  468.         messagebutton = gtk.Button()
  469.         messagebutton.set_label("Send")
  470.         messagebutton.connect("clicked", self.send_message, "message")
  471.         messagebox.pack_start(messagebutton, False, False, 0)
  472.         messagebutton.show()
  473.  
  474.         rightwin.pack_start(messagebox, False, False, 0)
  475.         messagebox.show()
  476.  
  477.         self.statusview.set_editable(False)
  478.         self.statusview.set_cursor_visible(True)
  479.         self.statusview.set_wrap_mode(gtk.WRAP_CHAR)
  480.         self.statusview.set_justification(gtk.JUSTIFY_LEFT)
  481.         self.statusview.modify_font(smallfont)
  482.  
  483.         self.statusbuffer.set_text("Position\nEnter your lat/long.  You may leave the decimal minutes (mm)\nblank for position ambiguity.  If you do not know your lat/long,\nenter your 5 digit zip code instead.\n\nBeacon\nWhen selected, sends location data every 10 minutes.  The\nbeacon will automatically stop when you edit personal\ninformation.  Must be manually reselected after edit completion.\n\nThis message will self destruct when you press Connect.\n\nAPRS Copyright (c) Bob Bruninga WB4APR\n\n")
  484.  
  485.         self.statuswindow = gtk.ScrolledWindow()
  486.         self.statuswindow.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_ALWAYS)
  487.         self.statuswindow.add_with_viewport(self.statusview)
  488.         self.statusview.show()
  489.         rightwin.pack_start(self.statuswindow, True, True, 0)
  490.         self.statuswindow.show()
  491.  
  492.         self.rawbox = gtk.HBox(False, 6)
  493.  
  494.         self.rawtext = gtk.Entry()
  495.         self.rawtext.set_max_length(128)
  496.         self.rawtext.set_width_chars(43)
  497.         self.rawtext.modify_font(smallfont)
  498.         self.rawtext.connect("activate", self.raw_send)
  499.         self.rawbox.pack_start(self.rawtext, False, False, 0)
  500.         self.rawtext.show()
  501.  
  502.         rawbutton = gtk.Button()
  503.         rawbutton.set_label("Send")
  504.         rawbutton.connect("clicked", self.raw_send)
  505.         self.rawbox.pack_start(rawbutton, False, False, 0)
  506.         rawbutton.show()
  507.  
  508.         rightwin.pack_start(self.rawbox, False, False, 0)
  509.         self.rawbox.show()
  510.  
  511.         win.pack_start(rightwin, False, False, 0)
  512.         rightwin.show()
  513.  
  514.         win.show()
  515.         self.calltext.grab_focus()
  516.  
  517.     def clear_status(self, button=None):
  518. #        self.statusbuffer.delete(self.statusbuffer.get_start_iter(), self.statusbuffer.get_end_iter())
  519.         self.statusbuffer.set_text("")
  520.  
  521.     def clear_message(self, button=None):
  522. #        self.messagebuffer.delete(self.messagebuffer.get_start_iter(), self.messagebuffer.get_end_iter())
  523.         self.messagebuffer.set_text("")
  524.  
  525.     def connect_aprs(self, button):
  526.         if (self.sock == None):
  527.  
  528.             if (self.help):
  529.                 self.clear_status()
  530.                 self.messagebuffer.set_text("Message Window")
  531.                 self.messagebox = True
  532.                 self.help = False
  533.  
  534.             if (self.validate_data() == False):
  535.                 return False
  536.             self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  537.             self.status_write("Connecting ")
  538.             iplist = socket.gethostbyname_ex(HOST)[2]
  539.             server = random.choice(iplist)
  540.             self.status_write("to %s\n" % server)
  541.             try:
  542.                 self.sock.connect((server, PORT))
  543.             except socket.error, msg:
  544.                 self.status_write(msg[1])
  545.                 self.sock = None
  546.                 return
  547.  
  548.             self.status_write("Connected\n")
  549.             button.set_label("Disconnect")
  550.             button.modify_bg(gtk.STATE_NORMAL, gtk.gdk.color_parse("#e6000a"))
  551.        
  552.             response = self.sock.recv(RECV_BUFFER)
  553.             self.status_write("%s" % response)
  554.             if (response.find("javAPRSSrvr") == -1):
  555.                 self.status_write("invalid response.\n")
  556.                 self.disconnect_aprs(button)
  557.  
  558.             if (response.find("Port Full") != -1):
  559.                 self.status_write("Port Full.\n")
  560.                 self.disconnect_aprs(button)
  561.  
  562.             if (self.calltext.get_text() != "" and self.passtext.get_text() != ""):
  563.                 sendme = "user %s pass %s vers aprs_xo %d filter m/300\n" % (self.calltext.get_text(), self.passtext.get_text(), VERSION)
  564.             else:
  565.                 sendme = "user %s vers aprs_xo %d\n" % (self.calltext.get_text(), VERSION)
  566.             self.sock.sendall(sendme)
  567.             self.status_write("%s" % sendme)
  568.  
  569.             response = self.sock.recv(RECV_BUFFER)
  570.             self.status_write("%s" % response)
  571.             if (response.find("# logresp") == -1):
  572.                 self.status_write("invalid response.\n")
  573.                 self.disconnect_aprs(button)
  574. #            if (response.find("unverified") == -1):
  575. #                self.rawbox.show()
  576.  
  577.             self.status_write("\n")
  578.  
  579.             self.input_watch = gobject.io_add_watch(self.sock, gobject.IO_IN, self.recv_data)
  580.  
  581.             # send banner
  582.             sendme = "%s>APRS-XO v%s\n" % (self.calltext.get_text(), VERSION)
  583.             self.sock.sendall(sendme)
  584.             self.status_write("%s>\n" % self.calltext.get_text())
  585.             self.status_write("APRS-XO v%s\n\n" % VERSION)
  586.  
  587.             self.send_beacon()
  588.             self.output_watch = gobject.timeout_add(10 * 60 * 1000, self.send_beacon)
  589.         else:
  590.             self.disconnect_aprs(button)
  591.             gobject.source_remove(self.input_watch)
  592.             gobject.source_remove(self.output_watch)
  593.  
  594.     def disconnect(self):
  595.         # for test server only
  596.         if(HOST == "192.168.50.6"):
  597.             self.sock.sendall("q")
  598.  
  599.         self.sock.close()
  600.         self.sock = None
  601.  
  602.     def disconnect_aprs(self, button):
  603.         self.disconnect()
  604.  
  605.         self.status_write("Disconnected\n")
  606.         button.set_label("Connect")
  607.         button.modify_bg(gtk.STATE_NORMAL, gtk.gdk.color_parse("#00b20d"))
  608. #        self.rawbox.hide()
  609.  
  610.     def recv_data(self, sock, condition):
  611.         while 1:
  612.             try:
  613.                 recv_data = sock.recv(RECV_BUFFER)
  614.             except:
  615.                 self.status_write("Server closed connection.\n")
  616.                 self.sock = None
  617.                 return False
  618.             if not recv_data:
  619.                 self.status_write("Server closed connection.\n")
  620.                 self.sock = None
  621.                 return False
  622.             else:
  623.                 # sometimes more than one packet comes in
  624.                 for packet in recv_data.split("\n"):
  625.                     if (len(packet) < 2):
  626.                         break
  627.                     # find uri's http or else www
  628.                     webaddy = packet.lower().find("http")
  629.                     if (webaddy != -1):
  630.                         packet = ("%s\n%s" % (packet[:webaddy], packet[webaddy:]))
  631.                     else:
  632.                         webaddy = packet.lower().find("www")
  633.                         if (webaddy != -1):
  634.                             packet = ("%s\n%s" % (packet[:webaddy], packet[webaddy:]))
  635.                     if (packet[0] == "#"):
  636.                         self.status_write("%s\n\n" % packet)
  637.                     else:
  638.                         cuthere = packet.find(":")
  639.                         self.status_write("%s\n" % packet[:cuthere+1])
  640.                         self.status_write("%s\n\n" % packet[cuthere+1:])
  641.                         self.msg_check(packet)
  642.                 return True
  643.  
  644.     def send_beacon(self):
  645.         if (self.beaconbutton.get_active()):
  646.             beacon = "=%s%s.%s%sX%s%s.%s%sA%s" % (self.latDDtext.get_text(), self.latMMtext.get_text(), self.latmmtext.get_text(), self.latcombo.get_active_text(), self.lonDDDtext.get_text(), self.lonMMtext.get_text(), self.lonmmtext.get_text(), self.loncombo.get_active_text(), self.stationtext.get_text())
  647.             if (self.send_data(beacon)):
  648.                 return True
  649.             else:
  650.                 self.status_write("\nProblem sending beacon - STOPPED")
  651.                 return False
  652.  
  653.     def send_data(self, msg):
  654.         path = "%s>APOLPC:" % self.calltext.get_text()
  655.         try:
  656.             self.sock.sendall("%s%s\n" % (path, msg))
  657.         except:
  658.             self.status_write("Problem sending packet\n")
  659.             self.sock = None
  660.             return False
  661.         self.status_write("%s\n" % path)
  662.         self.status_write("%s\n\n" % msg)
  663.         return True
  664.  
  665.     def send_ack(self, tocall, sequence):
  666.         # should check for duplicate messages in the next 30 seconds and drop them
  667.         # aprsd does this for us - but should do it here as well
  668.         if (tocall in self.sent_acks):
  669.             currentack = self.sent_acks[tocall]
  670.             if (currentack == sequence):
  671.                 ackmessage = ":%s:ack%s" % (tocall, sequence)
  672.                 self.send_data(ackmessage)
  673.         return False
  674.  
  675.     def status_write(self, text):
  676.         self.statusbuffer.insert(self.statusbuffer.get_end_iter(), text)
  677.         statuslines = self.statusbuffer.get_line_count()
  678.         if (statuslines > MAXLINES):
  679.             deletehere = self.statusbuffer.get_iter_at_line(statuslines - MAXLINES)
  680.             self.statusbuffer.delete(self.statusbuffer.get_start_iter(), deletehere)
  681.         if (not self.statusview.is_focus()):
  682.             adjustment = self.statuswindow.get_vadjustment()
  683.             adjustment.set_value(adjustment.upper)
  684.  
  685.     def message_write(self, text):
  686.         if (self.messagebox):
  687.             self.clear_message()
  688.             self.messagebox = False
  689.         self.messagebuffer.insert(self.messagebuffer.get_end_iter(), text)
  690.         if (not self.messageview.is_focus()):
  691.             adjustment = self.messagewindow.get_vadjustment()
  692.             adjustment.set_value(adjustment.upper)
  693.  
  694.     def set_location(self, widget, data=None):
  695.         self.location = data
  696.  
  697.     def set_site(self, widget, data=None):
  698.         self.site = data
  699.         self.clipboard()
  700.  
  701.     def validate_data(self):
  702.         stop_here = False
  703.         self.validating = True
  704.         beaconchecked = self.beaconbutton.get_active()
  705.  
  706.         if (self.calltext.get_text() == "" and self.ziptext.get_text() == ""):
  707.             self.status_write("A callsign or zip code must be provided.\n")
  708.             return False
  709.  
  710.         # convert zip to position
  711.         if (self.ziptext.get_text() != ""):
  712.             self.status_write("Attempting to set location from zip code\n")
  713.             self.ziptext.set_text(self.ziptext.get_text().zfill(5))
  714.             try:
  715.                 zipsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  716.             except socket.error, msg:
  717.                 self.status_write("[ERROR] %s\n" % msg[1])
  718.                 self.validating = False
  719.                 return False
  720.             try:
  721.                 zipsock.connect(('geocoder.us', 80))
  722.             except socket.error, msg:
  723.                 self.status_write("[ERROR] %s\n" % msg[1])
  724.                 self.validating = False
  725.                 return False
  726.             try:
  727.                 zipsock.sendall("GET /service/csv/geocode?zip=%s\n" % self.ziptext.get_text())
  728.             except socket.error, msg:
  729.                 self.status_write("[ERROR] %s\n" % msg[1])
  730.                 self.validating = False
  731.                 return False
  732.             zipsock.shutdown(1)
  733.             try:
  734.                 response = zipsock.recv(RECV_BUFFER)
  735.             except socket.error, msg:
  736.                 self.status_write("[ERROR] %s\n" % msg[1])
  737.                 self.validating = False
  738.                 return False
  739.             A = random.randrange(0, 9)
  740.             O = random.randrange(0, 9)
  741.             if (self.calltext.get_text() == ""):
  742.                 self.calltext.set_text("X%s-%d%d" % (self.ziptext.get_text(), A, O))
  743.             try:
  744.                 (lat, lon, junk) = response.split(',', 2)
  745.             except:
  746.                 self.status_write("%s\n" % response)
  747.                 lat = "0"
  748.                 lon = "0"
  749.             lat = float(lat.strip())
  750.             lon = float(lon.strip())
  751.             if (lat < 0):
  752.                 self.latcombo.set_active(1)     # S
  753.             else:
  754.                 self.latcombo.set_active(0)     # N
  755.             lat = abs(lat)
  756.             if (lon < 0):
  757.                 self.loncombo.set_active(0)     # W
  758.             else:
  759.                 self.loncombo.set_active(1)     # E
  760.             lon = abs(lon)
  761.             self.latDDtext.set_text("%02d" % int(lat))
  762.             self.latMMtext.set_text("%02d" % int((lat - int(lat)) * 60 + 0.5))
  763.             self.latmmtext.set_text("%d " % A)
  764.             self.lonDDDtext.set_text("%03d" % int(lon))
  765.             self.lonMMtext.set_text("%02d" % int((lon - int(lon)) * 60 + 0.5))
  766.             self.lonmmtext.set_text("%d " % O)
  767.             self.ziptext.set_text("")
  768.         # end set loc by zip code
  769.  
  770.         if (self.latDDtext.get_text() == "DD" or self.latDDtext.get_text() == ""):
  771.             self.status_write("Latitude Degrees are required.\n")
  772.             stop_here = True
  773.         if (self.latMMtext.get_text() == "MM" or self.latMMtext.get_text() == ""):
  774.             self.status_write("Latitude Minutes are required.\n")
  775.             stop_here = True
  776.         if (self.lonDDDtext.get_text() == "DDD" or self.lonDDDtext.get_text() == ""):
  777.             self.status_write("Longitude Degrees are required.\n")
  778.             stop_here = True
  779.         if (self.lonMMtext.get_text() == "MM" or self.lonMMtext.get_text() == ""):
  780.             self.status_write("Longitude Minutes are required.\n")
  781.             stop_here = True
  782.  
  783.         if (stop_here):
  784.             self.status_write("Latitude and Longitude must be complete.\nFor position ambiguity omit decimal Minutes (mm).\nIf you do not know your lat/long, leave the letters\n(DD, MM, etc) and provide your zip code.\n")
  785.             self.validating = False
  786.             return False
  787.  
  788.         if (not self.latDDtext.get_text().isdigit()):
  789.             self.status_write("Latitude Degrees must be a number.\n")
  790.             stop_here = True
  791.         if (not self.latMMtext.get_text().isdigit()):
  792.             self.status_write("Latitude Minutes must be a number.\n")
  793.             stop_here = True
  794.         if (not self.lonDDDtext.get_text().isdigit()):
  795.             self.status_write("Longitude Degrees must be a number.\n")
  796.             stop_here = True
  797.         if (not self.lonMMtext.get_text().isdigit()):
  798.             self.status_write("Longitude Minutes must be a number.\n")
  799.             stop_here = True
  800.  
  801.         if (stop_here):
  802.             self.status_write("Invalid Position.\n")
  803.             self.validating = False
  804.             return False
  805.  
  806.         if (int(self.latDDtext.get_text()) < 0  or int(self.latDDtext.get_text()) > 90):
  807.             self.status_write("Latitude Degrees must be between 0 and 90.\n")
  808.             stop_here = True
  809.         if (int(self.latMMtext.get_text()) < 0  or int(self.latMMtext.get_text()) > 60):
  810.             self.status_write("Latitude Minutes must be between 0 and 60.\n")
  811.             stop_here = True
  812.         if (int(self.lonDDDtext.get_text()) < 0  or int(self.lonDDDtext.get_text()) > 180):
  813.             self.status_write("Longitude Degrees must be between 0 and 180.\n")
  814.             stop_here = True
  815.         if (int(self.lonMMtext.get_text()) < 0  or int(self.lonMMtext.get_text()) > 60):
  816.             self.status_write("Longitude Minutes must be between 0 and 60.\n")
  817.             stop_here = True
  818.  
  819.         if (stop_here):
  820.             self.status_write("Invalid Position.\n")
  821.             self.validating = False
  822.             return False
  823.  
  824.         # clean up entries
  825.         self.latDDtext.set_text(self.latDDtext.get_text().zfill(2))
  826.         self.latMMtext.set_text(self.latMMtext.get_text().zfill(2))
  827.         self.lonDDDtext.set_text(self.lonDDDtext.get_text().zfill(3))
  828.         self.lonMMtext.set_text(self.lonMMtext.get_text().zfill(2))
  829.         self.latmmtext.set_text(self.latmmtext.get_text().ljust(2))
  830.         self.lonmmtext.set_text(self.lonmmtext.get_text().ljust(2))
  831.         if (self.latmmtext.get_text() == "mm"):
  832.             self.latmmtext.set_text("  ")
  833.         if (self.lonmmtext.get_text() == "mm"):
  834.             self.lonmmtext.set_text("  ")
  835.         self.calltext.set_text(self.calltext.get_text().upper())
  836.         self.beaconbutton.set_active(beaconchecked)
  837.         self.validating = False
  838.  
  839.     def close( self ):
  840.         self.hide()
  841.         if (self.sock != None):
  842.             self.disconnect()
  843.         activity.Activity.close( self )
  844.  
  845.     def write_file_broken(self, filename):
  846.         # does not appear to run
  847.         # from self.save() returns:
  848.         # TypeError: object of type 'dbus.Int32' has no len()
  849.  
  850.         self.metadata['mime_type'] = 'text/plain'
  851.  
  852.         self.metadata['callsign'] = self.calltext.get_text()
  853.         self.metadata['password'] = self.passtext.get_text()
  854.         self.metadata['latDD'] = self.latDDtext.get_text()
  855.         self.metadata['latMM'] = self.latMMtext.get_text()
  856.         self.metadata['latmm'] = self.latmmtext.get_text()
  857.         self.metadata['lat'] = self.latcombo.get_active()
  858.         self.metadata['lonDDD'] = self.lonDDDtext.get_text()
  859.         self.metadata['lonMM'] = self.lonMMtext.get_text()
  860.         self.metadata['lonmm'] = self.lonmmtext.get_text()
  861.         self.metadata['lon'] = self.loncombo.get_active()
  862. #        self.metadata['nick_name'] = self.nametext.get_text()
  863. #        self.metadata['location'] = self.location
  864.         self.metadata['stationtext'] = self.stationtext.get_text()
  865. #        self.metadata['zip'] = self.ziptext.get_text()
  866.         self.metadata['beacon'] = self.beaconbutton.get_active()
  867.         # should save list of callsigns used in outgoing messages
  868.  
  869.     def read_file_broken(self, filename):
  870.         # does not appear to run
  871.         self.calltext.set_text(self.metadata.get('callsign', ""))
  872.         self.passtext.set_text(self.metadata.get('password', ""))
  873.         self.latDDtext.set_text(self.metadata.get('latDD', "00"))
  874.         self.latMMtext.set_text(self.metadata.get('latMM', "00"))
  875.         self.latmmtext.set_text(self.metadata.get('latmm', "00"))
  876. #        self.latcombo.set_active(self.metadata.get('lat', ""))
  877.         self.lonDDDtext.set_text(self.metadata.get('lonDDD', "000"))
  878.         self.lonMMtext.set_text(self.metadata.get('lonMM', "00"))
  879.         self.lonmmtext.set_text(self.metadata.get('lonmm', "00"))
  880. #        self.loncombo.set_active(self.metadata.get('lon', ""))
  881. #        self.nametext.set_text(self.metadata.get('nick_name', "Joe"))
  882. #        self.locbutton.set_active(self.metadata.get('location', ""))
  883.         self.stationtext.set_text(self.metadata.get('stationtext', "test text"))
  884.         self.ziptext.set_text(self.metadata.get('zip', ""))
  885. #        self.beaconbutton.set_active(self.metadata.get('beacon', ""))
  886.  
  887.     def msg_check(self, data):
  888.         # a better message decoder
  889.         firstcheck = data.find("::")
  890.         secondcheck = data[firstcheck+11:firstcheck+12]
  891.         thirdcheck = data.find("{")
  892.         if (firstcheck != -1 and secondcheck == ":"):
  893.             tocall = data[firstcheck+2:firstcheck+11].upper()
  894.             strippedtocall = tocall.strip()
  895.             fromcall = data[:data.find(">")].upper()
  896.             isbulletin = self.bulletin_check(strippedtocall)
  897.             if (isbulletin):
  898.                 message = data[firstcheck+12:]
  899.                 self.add_callsign(fromcall, False)
  900.                 bln_id = "%s-%s" % (fromcall, strippedtocall)
  901.                 if (not bln_id in self.seen_bulletins):
  902.                     self.seen_bulletins[bln_id] = 1
  903.                     self.message_write("%s %s:%s> %s\n" % (time.strftime("%m/%d %H:%M", time.localtime()), fromcall, strippedtocall, message))
  904.             else:
  905.                 if (strippedtocall == self.calltext.get_text()):
  906.                     self.add_callsign(fromcall, False)
  907.                     if (thirdcheck != -1):
  908.                         sequence_end = data.find("}")
  909.                         if (sequence_end == -1):
  910.                             sequence = data[thirdcheck + 1:]
  911.                         else:
  912.                             sequence = data[thirdcheck + 1:sequence_end]
  913.                             replyack = data[sequence_end + 1:]
  914.                             if (len(replyack) > 1):
  915.                                 ackid = "%s-%s" % (fromcall, replyack)
  916.                                 if (ackid in self.recv_acks):
  917.                                     if (self.recv_acks[ackid] == 0):
  918.                                         count_start = self.messagebuffer.get_iter_at_mark(self.message_marks[ackid])
  919.                                         count_end = count_start.forward_search(">", gtk.TEXT_SEARCH_TEXT_ONLY)
  920.                                         self.messagebuffer.delete(count_start, count_end[0])
  921.                                         self.send_msg_queue(fromcall)
  922.                                     self.recv_acks[ackid] = 1
  923.                         message = data[firstcheck+12:thirdcheck]
  924. #                        ackmessage = ":%s:ack%s" % (fromcall, sequence)
  925.                         ackmessage = ":%s:ack%s" % (fromcall, data[thirdcheck + 1:])
  926.                         self.send_data(ackmessage)
  927.                         id = "%s-%s" % (fromcall, sequence)
  928.                         if (id in self.sent_acks):
  929.                             self.timers.append(gobject.timeout_add(30 * 1000, self.send_ack, tocall, sequence))
  930.                             self.timers.append(gobject.timeout_add(60 * 1000, self.send_ack, tocall, sequence))
  931.                             self.timers.append(gobject.timeout_add(120 * 1000, self.send_ack, tocall, sequence))
  932.                         else:
  933.                             # TODO beep?
  934.                             self.message_write("%s %s> %s\n" % (time.strftime("%m/%d %H:%M", time.localtime()), fromcall, message))
  935.                         self.sent_acks[id] = time.time() # to help a cleanup thread later
  936.                         self.sent_acks[fromcall] = sequence # to help with reply acks later
  937.                     else:
  938.                         message = data[firstcheck+12:]
  939.                         if (message[0:3] == "ack"):
  940.                             sequence_end = message.find("}")
  941.                             if (sequence_end == -1):
  942.                                 sequence = message[3:]
  943.                             else:
  944.                                 sequence = message[3:sequence_end]
  945.                             ackid = "%s-%s" % (fromcall, sequence)
  946.                             if (ackid in self.recv_acks):
  947.                                 if (self.recv_acks[ackid] == 0):
  948.                                     count_start = self.messagebuffer.get_iter_at_mark(self.message_marks[ackid])
  949.                                     count_end = count_start.forward_search(">", gtk.TEXT_SEARCH_TEXT_ONLY)
  950.                                     self.messagebuffer.delete(count_start, count_end[0])
  951.                                     self.send_msg_queue(fromcall)
  952.                             self.recv_acks[ackid] = 1
  953.                         else:
  954.                             # TODO beep?
  955.                             self.message_write("%s %s> %s\n" % (time.strftime("%m/%d %H:%M", time.localtime()), fromcall, message))
  956.  
  957.     def disable_beacon(self, widget, data=None):
  958.         if (self.sock != None):
  959.             self.beaconbutton.set_active(False)
  960.  
  961.     def enable_beacon(self, widget, data=None):
  962.         if (not self.validating and widget.get_active()):
  963.             self.validate_data()
  964.  
  965.     def raw_send(self, widget, data=None):
  966.         msg = "%s\n" % self.rawtext.get_text()
  967.         try:
  968.             self.sock.sendall(msg)
  969.         except:
  970.             self.status_write("Problem sending message\n")
  971.             self.sock = None
  972.             return False
  973.         self.status_write("%s\n" % msg)
  974.         self.rawtext.set_text("")
  975.         return True
  976.  
  977.     def send_message(self, widget, data=None):
  978.         sendnow = False
  979.  
  980.         tocall = self.messagedest.get_text().upper()
  981.         message = self.messagetext.get_text()
  982.  
  983.         if (message == ""):
  984.             return False
  985.  
  986.         isbulletin = self.bulletin_check(tocall)
  987.  
  988.         # check for illegal characters before clearing message
  989.         if (isbulletin):
  990.             if (message.find("|") != -1 or message.find("~") != -1):
  991.                 self.message_write("Bulletins can not contain the following characters: | ~")
  992.                 return False
  993.         else:
  994.             if (message.find("|") != -1 or message.find("~") != -1 or message.find("{") != -1):
  995.                 self.message_write("Messages can not contain the following characters: | ~ {")
  996.                 return False
  997.  
  998.         # add callsign to list if just entered
  999.         if (self.messagecombo.get_active() == -1):
  1000.             self.add_callsign(tocall, True)
  1001.  
  1002.         # get a sequence number
  1003.         if (isbulletin):
  1004.             # don't waste a number on a bulletin
  1005.             sequence = ""
  1006.         else:
  1007.             sequence = "%s" % self.b90()
  1008.  
  1009.         # TODO
  1010.         # cancel message option - menu popup
  1011.  
  1012.         # is there a queue for this callsign?
  1013.         if (tocall not in self.queue_list):
  1014.             sendnow = True
  1015.  
  1016.         # add the message to the queue
  1017.         if (not self.msg_queue(tocall, sequence, message)):
  1018.             if (self.sequence > 0):
  1019.                 self.sequence -= 1
  1020.             return False
  1021.  
  1022.         # clear the message text box
  1023.         self.messagetext.set_text("")
  1024.  
  1025.         if (sendnow):
  1026.             self.send_msg_queue(tocall)
  1027.  
  1028.     def b90(self):
  1029.         if (self.sequence > 8099):
  1030.             self.sequence = 0
  1031.         first = ((self.sequence / 90) % 90) + 33
  1032.         second = (self.sequence % 90) + 33
  1033.         output = "%c%c" % (first, second)
  1034.         self.sequence += 1
  1035.         return output
  1036.  
  1037.     def msg_timer(self, tocall, message, sequence, count, delay):
  1038.         id = "%s-%s" % (tocall, sequence)
  1039.         if (self.recv_acks[id] == 1):
  1040.             return False
  1041.         else:
  1042.             replyack = self.replyack(tocall)
  1043.             self.send_data(":%s:%s{%s}%s" % (tocall.ljust(9), message, sequence, replyack))
  1044.  
  1045.             count_start = self.messagebuffer.get_iter_at_mark(self.message_marks[id])
  1046.             count_end = self.messagebuffer.get_iter_at_mark(self.message_marks[id])
  1047.             count_end.forward_chars(len(str(count - 1)) + 1)
  1048.             self.messagebuffer.delete(count_start, count_end)
  1049.             iter = self.messagebuffer.get_iter_at_mark(self.message_marks[id])
  1050.             self.messagebuffer.insert(iter, " %i" % count)
  1051.  
  1052.             # start the next timer
  1053.             count += 1
  1054.             if (count > MAXRETRIES):
  1055.                 self.recv_acks[id] = 1
  1056.  
  1057.                 count_start = self.messagebuffer.get_iter_at_mark(self.message_marks[id])
  1058.                 count_end = self.messagebuffer.get_iter_at_mark(self.message_marks[id])
  1059.                 count_end.forward_chars(len(str(count - 1)) + len(str(MAXRETRIES)) + 2)
  1060.                 self.messagebuffer.delete(count_start, count_end)
  1061.  
  1062.                 line_end = self.messagebuffer.get_iter_at_mark(self.message_marks[id])
  1063.                 line_end.forward_to_line_end()
  1064.                 self.messagebuffer.insert(line_end, " * TIMEOUT *")
  1065.  
  1066.                 self.send_msg_queue(tocall)
  1067.                 return False
  1068.             delay *=  2
  1069.             if (delay > 600):
  1070.                 delay = 600
  1071.             gobject.timeout_add(delay * 1000, self.msg_timer, tocall, message, sequence, count, delay)
  1072.  
  1073.             # and stop this timer
  1074.             return False
  1075.  
  1076.     def clipboard(self):
  1077.         clipboard = gtk.clipboard_get()
  1078.         target = [("text/uri-list", 0, 0)]
  1079.         clipboard.set_with_data(target, self.clipboard_get, self.clipboard_clear, (self.site))
  1080.  
  1081.     def clipboard_get(self, clipboard, selection, info, data):
  1082.         selection.set_uris([data])
  1083.  
  1084.     def clipboard_clear(self, clipboard, data):
  1085.         pass
  1086.  
  1087.     def add_callsign(self, callsign, activate):
  1088.         model = self.messagecombo.get_model()
  1089.         notfound = True
  1090.         iter = model.get_iter_first()
  1091.         while iter:
  1092.             currentcall = model.get(iter, 0)[0]
  1093.             if (currentcall == callsign):
  1094.                 notfound = False
  1095.                 break
  1096.             iter = model.iter_next(iter)
  1097.         if (notfound):
  1098.             self.messagecombo.prepend_text(callsign)
  1099.             if (activate):
  1100.                 self.messagecombo.set_active(0)
  1101.  
  1102.     def bulletin_check(self, callsign):
  1103.         for currentcall in self.bulletins:
  1104.             length = len(currentcall)
  1105.             if (currentcall == callsign[:length]):
  1106.                 return True
  1107.         return False
  1108.  
  1109.     def replyack(self, tocall):
  1110.         replyack = ""
  1111.         if (tocall in self.sent_acks):
  1112.             id = "%s-%s" % (tocall, self.sent_acks[tocall])
  1113.             if (time.time() - self.sent_acks[id] < 5400):
  1114.                 # less than 90 minutes ago
  1115.                 replyack = self.sent_acks[tocall]
  1116.         return replyack
  1117.  
  1118.     def msg_queue(self, call, sequence, message):
  1119.         if (len(self.message_list) >= MAX_MSG_QUEUE):
  1120.             self.message_write("Too many messages in queue.\n")
  1121.             return False
  1122.         id = "%s-%s" % (call, sequence)
  1123.         if (call in self.queue_list):
  1124.             if (len(self.queue_list[call]) >= MAX_PER_CALL_QUEUE):
  1125.                 self.message_write("Too many messages to %s in queue.\n" % call)
  1126.                 return False
  1127.             self.queue_list[call].append(sequence)
  1128.             self.message_list[id] = message
  1129.         else:
  1130.             self.queue_list[call] = []
  1131.             self.queue_list[call].append(sequence)
  1132.             self.message_list[id] = message
  1133.         self.message_write("%s To:%s> %s * QUEUED *\n" % (time.strftime("%m/%d %H:%M", time.localtime()), call, message))
  1134.         # if a message is received before the next lines are run there will be problems...
  1135.         iter = self.messagebuffer.get_iter_at_line(self.messagebuffer.get_line_count() - 2)
  1136.         iter.forward_chars(15 + len(call))
  1137.         self.message_marks[id] = self.messagebuffer.create_mark(None, iter, True)
  1138. #        self.message_marks[id].set_visible(True)
  1139.         return True
  1140.  
  1141.     def send_msg_queue(self, call):
  1142.         if (call in self.queue_list):
  1143.             if (self.queue_list[call] == []):
  1144.                 del self.queue_list[call]
  1145.                 return False
  1146.             sequence = self.queue_list[call].pop(0)
  1147.             id = "%s-%s" % (call, sequence)
  1148.             message = self.message_list[id]
  1149.             del self.message_list[id]
  1150.  
  1151.             # send message
  1152.             isbulletin = self.bulletin_check(call)
  1153.             if (isbulletin):
  1154.                 self.send_data(":%s:%s" % (call.ljust(9), message))
  1155.                 # TODO what are the correct timings for bulletins?
  1156.                 gobject.timeout_add(600 * 1000, self.msg_timer, call, message, "", 2, 600)
  1157.             else:
  1158.                 replyack = self.replyack(call)
  1159.                 self.send_data(":%s:%s{%s}%s" % (call.ljust(9), message, sequence, replyack))
  1160.                 self.recv_acks["%s-%s" % (call, sequence)] = 0
  1161.                 gobject.timeout_add(7 * 1000, self.msg_timer, call, message, sequence, 2, 7)
  1162.  
  1163.             # clear "QUEUED" and add the counter
  1164.             line_end = self.messagebuffer.get_iter_at_mark(self.message_marks[id])
  1165.             queue_start = self.messagebuffer.get_iter_at_mark(self.message_marks[id])
  1166.             line_end.forward_to_line_end()
  1167.             queue_start.forward_to_line_end()
  1168.             queue_start.backward_chars(11)
  1169.             self.messagebuffer.delete(queue_start, line_end)
  1170.             iter = self.messagebuffer.get_iter_at_mark(self.message_marks[id])
  1171.             self.messagebuffer.insert(iter, " 1/%s" % MAXRETRIES)
  1172.