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