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. import json
  26.  
  27. from gettext import gettext as _
  28.  
  29. from sugar import profile
  30. from sugar.activity import activity
  31. from sugar.bundle.activitybundle import ActivityBundle
  32. from sugar.graphics.menuitem import MenuItem
  33. from sugar.graphics.toolbutton import ToolButton
  34.  
  35. HOST = 'rotate.aprs2.net'
  36. #HOST = '192.168.50.6'
  37. PORT = 14580
  38. RECV_BUFFER = 4096
  39.  
  40. MAXLINES = 50
  41. MAXRETRIES = 15
  42. MAX_MSG_QUEUE = 25
  43. MAX_PER_CALL_QUEUE = 2
  44.  
  45. FILTER = "m/300"
  46.  
  47. bundle = ActivityBundle(activity.get_bundle_path())
  48. VERSION = bundle.get_activity_version()
  49. del bundle
  50.  
  51. class APRSActivity(activity.Activity):
  52.     def __init__(self, handle):
  53.         activity.Activity.__init__(self, handle)
  54.         self.set_title(_('APRS-XO Activity'))
  55.  
  56.         self.sock = None
  57.         self.location = "home"
  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.         self.input_watch = []
  71.         self.output_watch = []
  72.         self.cq_watch = []
  73.         self.current_message = {}
  74.  
  75.         titlefont = pango.FontDescription('Sans bold 8')
  76.         mediumfont = pango.FontDescription('Sans 6.5')
  77.         smallfont = pango.FontDescription('Sans 6')
  78.         verysmallfont = pango.FontDescription('Sans 4')
  79.  
  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.  
  88.         toolbox.show()
  89.  
  90.         win = gtk.HBox(False, 10)
  91.  
  92.         leftwin = gtk.VBox(False, 10)
  93.  
  94.         # Top 'about' box
  95.         aboutbox = gtk.VBox(False, 11)
  96.         aboutbox.set_border_width(10)
  97.  
  98.         topaboutbox = gtk.VBox(False, 0)
  99.  
  100.         titlebox = gtk.HBox(False, 0)
  101.         titlelabel = gtk.Label("APRS-XO:")
  102.         titlelabel.set_alignment(0, 0)
  103.         titlelabel.modify_font(titlefont)
  104.         titlebox.pack_start(titlelabel, False, False, 0)
  105.         titlelabel.show()
  106.         aboutlabel1 = gtk.Label("This amateur radio program will update your")
  107.         aboutlabel1.set_alignment(0, 0.8)
  108. #        aboutlabel1.modify_font(mediumfont)
  109.         titlebox.pack_start(aboutlabel1, False, False, 0)
  110.         aboutlabel1.show()
  111.         topaboutbox.pack_start(titlebox, False, False, 0)
  112.         titlebox.show()
  113.         aboutlabel2 = gtk.Label("positon & status on all of the global APRS web pages once\nevery 10 minutes.")
  114.         aboutlabel2.set_alignment(0, 0)
  115. #        aboutlabel2.modify_font(mediumfont)
  116.         topaboutbox.pack_start(aboutlabel2, False, False, 0)
  117.         aboutlabel2.show()
  118.         aboutbox.pack_start(topaboutbox, False, False, 0)
  119.         topaboutbox.show()
  120.  
  121.         sitebox = gtk.HBox(False, 10)
  122.         sitelabel = gtk.Label("Select an APRS site:")
  123.         sitelabel.set_alignment(0, 0.4)
  124.         sitebox.pack_start(sitelabel, False, False, 0)
  125.         sitelabel.show()
  126.  
  127.         findubutton = gtk.Button()
  128.         findubutton.set_label("FindU")
  129.         findubutton.connect("clicked", self.open_url_button, "http://www.findu.com/cgi-bin/symbol.cgi?icon=XA&limit=200")
  130.         sitebox.pack_start(findubutton, False, False, 0)
  131.         findubutton.show()
  132.  
  133.         aprsworldbutton = gtk.Button()
  134.         aprsworldbutton.set_label("APRSworld")
  135.         aprsworldbutton.connect("clicked", self.open_url_button, "http://aprsworld.net/")
  136.         sitebox.pack_start(aprsworldbutton, False, False, 0)
  137.         aprsworldbutton.show()
  138.  
  139.         aprsbutton = gtk.Button()
  140.         aprsbutton.set_label("APRS")
  141.         aprsbutton.connect("clicked", self.open_url_button, "http://aprs.org/")
  142.         sitebox.pack_start(aprsbutton, False, False, 0)
  143.         aprsbutton.show()
  144.  
  145.         otherbutton = gtk.Button()
  146.         otherbutton.set_label("About")
  147.         otherbutton.connect("clicked", self.open_url_button, "http://zielkeassociates.com/~jack/aprs-xo/")
  148.         sitebox.pack_start(otherbutton, False, False, 0)
  149.         otherbutton.show()
  150.  
  151.         aboutbox.pack_start(sitebox, False, False, 0)
  152.         sitebox.show()
  153.  
  154.         leftwin.pack_start(aboutbox, False, False, 0)
  155.         aboutbox.show()
  156.  
  157.         separator = gtk.HSeparator()
  158.         leftwin.pack_start(separator, False, False, 0)
  159.         separator.show()
  160.  
  161.         # identifiers box
  162.         identbox = gtk.VBox(False, 4)
  163.         identbox.set_border_width(10)
  164.  
  165.         identlabel = gtk.Label("Identifiers")
  166.         identlabel.set_alignment(0, 0)
  167.         identlabel.modify_font(titlefont)
  168.         identbox.pack_start(identlabel, False, False, 0)
  169.         identlabel.show()
  170.  
  171.         bottomidentbox = gtk.HBox(False, 5)
  172.  
  173.         calllabel1 = gtk.Label("Callsign: ")
  174.         calllabel1.set_alignment(1, 0.5)
  175.         bottomidentbox.pack_start(calllabel1, False, False, 0)
  176.         calllabel1.show()
  177.  
  178.         self.calltext = gtk.Entry()
  179.         self.calltext.set_max_length(9)
  180.         self.calltext.set_width_chars(9)
  181.         self.calltext.set_text(self.metadata.get('callsign', ""))
  182.         self.calltext.connect("changed", self.disable_beacon)
  183.         bottomidentbox.pack_start(self.calltext, False, False, 0)
  184.         self.calltext.show()
  185.  
  186.         passlabel1 = gtk.Label("Password: ")
  187.         passlabel1.set_alignment(1, 0.5)
  188.         bottomidentbox.pack_start(passlabel1, False, False, 0)
  189.         passlabel1.show()
  190.  
  191.         self.passtext = gtk.Entry()
  192.         self.passtext.set_max_length(5)
  193.         self.passtext.set_width_chars(5)
  194.         self.passtext.set_invisible_char("x")
  195.         self.passtext.set_visibility(False)
  196.         bottomidentbox.pack_start(self.passtext, False, False, 0)
  197.         self.passtext.show()
  198.  
  199.         self.passbutton = gtk.CheckButton()
  200.         self.passbutton.set_active(True)
  201.         self.passbutton.connect("toggled", self.hide_password, "password")
  202.         bottomidentbox.pack_start(self.passbutton, False, False, 0)
  203.         self.passbutton.show()
  204.  
  205.         passbuttonbox = gtk.VBox(False, 0)
  206.  
  207.         passlabel3 = gtk.Label("Hide")
  208.         passlabel3.set_alignment(0.5, 0.5)
  209.         passlabel3.modify_font(smallfont)
  210.         passbuttonbox.pack_start(passlabel3, False, False, 0)
  211.         passlabel3.show()
  212.  
  213.         passlabel4 = gtk.Label("Password")
  214.         passlabel4.set_alignment(0, 0.5)
  215.         passlabel4.modify_font(smallfont)
  216.         passbuttonbox.pack_start(passlabel4, False, False, 0)
  217.         passlabel4.show()
  218.  
  219.         bottomidentbox.pack_start(passbuttonbox, False, False, 0)
  220.         passbuttonbox.show()
  221.  
  222.         identbox.pack_start(bottomidentbox, False, False, 0)
  223.         bottomidentbox.show()
  224.  
  225.         passlabel2 = gtk.Label("optional")
  226.         passlabel2.set_alignment(0.71, 0)
  227.         passlabel2.modify_font(smallfont)
  228.         identbox.pack_start(passlabel2, False, False, 0)
  229.         passlabel2.show()
  230.  
  231.         leftwin.pack_start(identbox, False, False, 0)
  232.         identbox.show()
  233.  
  234.         separator = gtk.HSeparator()
  235.         leftwin.pack_start(separator, False, False, 0)
  236.         separator.show()
  237.  
  238.         # station box
  239.         stationbox = gtk.VBox(False, 11)
  240.         stationbox.set_border_width(10)
  241.  
  242.         stationlabel = gtk.Label("Station Comment")
  243.         stationlabel.set_alignment(0, 0)
  244.         stationlabel.modify_font(titlefont)
  245.         stationbox.pack_start(stationlabel, False, False, 0)
  246.         stationlabel.show()
  247.  
  248.         # so the text box does not fill all horizontal space
  249.         stationtextbox = gtk.HBox()
  250.  
  251.         self.stationtext = gtk.Entry()
  252.         self.stationtext.set_max_length(43)
  253.         self.stationtext.set_width_chars(43)
  254.         self.stationtext.set_text("%s's XO at home." % firstName)
  255.         self.stationtext.connect("changed", self.disable_beacon)
  256.         stationtextbox.pack_start(self.stationtext, False, False, 0)
  257.         self.stationtext.show()
  258.         stationbox.pack_start(stationtextbox, False, False, 0)
  259.         stationtextbox.show()
  260.  
  261.         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.")
  262.         stationhelp.set_alignment(0, 0)
  263. #        stationhelp.modify_font(mediumfont)
  264.         stationbox.pack_start(stationhelp, False, False, 0)
  265.         stationhelp.show()
  266.  
  267.         leftwin.pack_start(stationbox, False, False, 0)
  268.         stationbox.show()
  269.  
  270.         separator = gtk.HSeparator()
  271.         leftwin.pack_start(separator, False, False, 0)
  272.         separator.show()
  273.  
  274.         # position box
  275.         positbox = gtk.VBox(False, 11)
  276.         positbox.set_border_width(10)
  277.  
  278.         toppositbox = gtk.HBox(False, 0)
  279.  
  280.         topleftpositbox = gtk.VBox(False, 4)
  281.  
  282.         positlabel1 = gtk.Label("Position Data")
  283.         positlabel1.set_alignment(0, 0)
  284.         positlabel1.modify_font(titlefont)
  285.         topleftpositbox.pack_start(positlabel1, False, False, 0)
  286.         positlabel1.show()
  287.  
  288.         latpositbox = gtk.HBox(False, 4)
  289.         latlabel1 = gtk.Label("Latitude:    ")
  290.         latlabel1.set_alignment(0, 0.5)
  291.         latpositbox.pack_start(latlabel1, False, False, 0)
  292.         latlabel1.show()
  293.  
  294.         self.latDDtext = gtk.Entry()
  295.         self.latDDtext.set_max_length(2)
  296.         self.latDDtext.set_width_chars(4)
  297.         self.latDDtext.set_text("DD")
  298.         self.latDDtext.connect("changed", self.disable_beacon)
  299.         latpositbox.pack_start(self.latDDtext, False, False, 0)
  300.         self.latDDtext.show()
  301.  
  302.         self.latMMtext = gtk.Entry()
  303.         self.latMMtext.set_max_length(2)
  304.         self.latMMtext.set_width_chars(3)
  305.         self.latMMtext.set_text("MM")
  306.         self.latMMtext.connect("changed", self.disable_beacon)
  307.         latpositbox.pack_start(self.latMMtext, False, False, 0)
  308.         self.latMMtext.show()
  309.  
  310.         latlabel2 = gtk.Label(".")
  311.         latlabel2.set_alignment(0, 1)
  312.         latpositbox.pack_start(latlabel2, False, False, 0)
  313.         latlabel2.show()
  314.  
  315.         self.latmmtext = gtk.Entry()
  316.         self.latmmtext.set_max_length(2)
  317.         self.latmmtext.set_width_chars(3)
  318.         self.latmmtext.set_text("mm")
  319.         self.latmmtext.connect("changed", self.disable_beacon)
  320.         latpositbox.pack_start(self.latmmtext, False, False, 0)
  321.         self.latmmtext.show()
  322.  
  323.         self.latcombo = gtk.combo_box_new_text()
  324.         self.latcombo.append_text("N")
  325.         self.latcombo.append_text("S")
  326.         self.latcombo.set_active(0)
  327.         latpositbox.pack_start(self.latcombo, False, False, 0)
  328.         self.latcombo.show()
  329.  
  330.         topleftpositbox.pack_start(latpositbox, False, False, 0)
  331.         latpositbox.show()
  332.  
  333.         lonpositbox = gtk.HBox(False, 4)
  334.         lonlabel1 = gtk.Label("Longitude: ")
  335.         lonlabel1.set_alignment(0, 0.5)
  336.         lonpositbox.pack_start(lonlabel1, False, False, 0)
  337.         lonlabel1.show()
  338.  
  339.         self.lonDDDtext = gtk.Entry()
  340.         self.lonDDDtext.set_max_length(3)
  341.         self.lonDDDtext.set_width_chars(4)
  342.         self.lonDDDtext.set_text("DDD")
  343.         self.lonDDDtext.connect("changed", self.disable_beacon)
  344.         lonpositbox.pack_start(self.lonDDDtext, False, False, 0)
  345.         self.lonDDDtext.show()
  346.  
  347.         self.lonMMtext = gtk.Entry()
  348.         self.lonMMtext.set_max_length(2)
  349.         self.lonMMtext.set_width_chars(3)
  350.         self.lonMMtext.set_text("MM")
  351.         self.lonMMtext.connect("changed", self.disable_beacon)
  352.         lonpositbox.pack_start(self.lonMMtext, False, False, 0)
  353.         self.lonMMtext.show()
  354.  
  355.         lonlabel2 = gtk.Label(".")
  356.         lonlabel2.set_alignment(0, 1)
  357.         lonpositbox.pack_start(lonlabel2, False, False, 0)
  358.         lonlabel2.show()
  359.  
  360.         self.lonmmtext = gtk.Entry()
  361.         self.lonmmtext.set_max_length(2)
  362.         self.lonmmtext.set_width_chars(3)
  363.         self.lonmmtext.set_text("mm")
  364.         self.lonmmtext.connect("changed", self.disable_beacon)
  365.         lonpositbox.pack_start(self.lonmmtext, False, False, 0)
  366.         self.lonmmtext.show()
  367.  
  368.         self.loncombo = gtk.combo_box_new_text()
  369.         self.loncombo.append_text("W")
  370.         self.loncombo.append_text("E")
  371.         self.loncombo.set_active(0)
  372.         lonpositbox.pack_start(self.loncombo, False, False, 0)
  373.         self.loncombo.show()
  374.  
  375.         topleftpositbox.pack_start(lonpositbox, False, False, 0)
  376.         lonpositbox.show()
  377.  
  378.         toppositbox.pack_start(topleftpositbox, False, False, 0)
  379.         topleftpositbox.show()
  380.  
  381.         toprightpositbox = gtk.VBox(False, 4)
  382.         toprightpositbox.set_border_width(10)
  383.  
  384.         loclabel = gtk.Label("-OR- Zip Code:")
  385.         loclabel.set_alignment(0.3, 0)
  386.         toprightpositbox.pack_start(loclabel, False, False, 0)
  387.         loclabel.show()
  388.  
  389.         self.ziptext = gtk.Entry()
  390.         self.ziptext.set_max_length(5)
  391.         self.ziptext.set_width_chars(5)
  392.         self.ziptext.connect("changed", self.disable_beacon)
  393.         toprightpositbox.pack_start(self.ziptext, False, False, 0)
  394.         self.ziptext.show()
  395.  
  396.         toppositbox.pack_start(toprightpositbox, False, False, 0)
  397.         toprightpositbox.show()
  398.  
  399.         positbox.pack_start(toppositbox, False, False, 0)
  400.         toppositbox.show()
  401.  
  402.         positlabel2 = gtk.Label("If you do not know your LAT/LONG then your zip code will\nbe used to place you on the map.")
  403.         positlabel2.set_alignment(0, 0)
  404. #        positlabel2.modify_font(mediumfont)
  405.         positbox.pack_start(positlabel2, False, False, 0)
  406.         positlabel2.show()
  407.  
  408.         leftwin.pack_start(positbox, False, False, 0)
  409.         positbox.show()
  410.  
  411.         separator = gtk.HSeparator()
  412.         leftwin.pack_start(separator, False, False, 0)
  413.         separator.show()
  414.  
  415.         # defined here so clear and connect buttons have access
  416.         self.statusview = gtk.TextView()
  417.         self.statusbuffer = self.statusview.get_buffer()
  418.         self.messageview = gtk.TextView()
  419.         self.messagebuffer = self.messageview.get_buffer()
  420.  
  421.         self.connectbutton = gtk.Button()
  422.         self.connectbutton.set_label("Connect")
  423.         self.connectbutton.modify_bg(gtk.STATE_NORMAL, gtk.gdk.color_parse("#00b20d"))
  424.         self.connectbutton.connect("clicked", self.connect_aprs)
  425.         leftwin.pack_start(self.connectbutton, False, False, 12)
  426.         self.connectbutton.show()
  427.  
  428.         win.pack_start(leftwin, False, False, 0)
  429.         leftwin.show()
  430.  
  431.         rightwin = gtk.VBox(False, 4)
  432.         rightwin.set_border_width(4)
  433.  
  434.         rightwintopbox = gtk.HBox(False, 4)
  435.  
  436.         clearbutton = gtk.Button()
  437. #        clearbutton.set_label(" Cancel Messages ")
  438. #        clearbutton.connect("clicked", self.clear_msg_queue)
  439.         clearbutton.set_label(" Clear/Cancel ")
  440.         clearbutton.connect("clicked", self.clear_message_button)
  441.         rightwintopbox.pack_start(clearbutton, False, False, 5)
  442.         clearbutton.show()
  443.  
  444.         self.cqbutton = gtk.CheckButton("CQ")
  445.         self.cqbutton.set_active(False)
  446.         self.cqbutton.connect("toggled", self.enable_cq, "cq")
  447.         rightwintopbox.pack_start(self.cqbutton, False, False, 5)
  448.         self.cqbutton.show()
  449.  
  450.         self.beaconbutton = gtk.CheckButton("Beacon every 10 minutes")
  451.         self.beaconbutton.set_active(True)
  452.         self.beaconbutton.connect("toggled", self.enable_beacon, "beacon")
  453.         rightwintopbox.pack_start(self.beaconbutton, False, False, 5)
  454.         self.beaconbutton.show()
  455.  
  456.         rightwin.pack_start(rightwintopbox, False, False, 0)
  457.         rightwintopbox.show()
  458.  
  459.         self.messageview.set_editable(False)
  460.         self.messageview.set_cursor_visible(True)
  461.         self.messageview.set_wrap_mode(gtk.WRAP_CHAR)
  462.         self.messageview.set_justification(gtk.JUSTIFY_LEFT)
  463.         self.messageview.modify_font(smallfont)
  464.  
  465.         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 journal.\n\nIndentifiers\nEnter your callsign.  If you leave the password blank it will be\nautomatically generated.\n\nStation Comment\nData in the Station Comment field will appear after your\nlocation information on the website.")
  466.  
  467.         # tags for easier reading of messages
  468.         self.messagebold = self.messagebuffer.create_tag("bold", weight=pango.WEIGHT_BOLD)
  469.  
  470.         self.messagewindow = gtk.ScrolledWindow()
  471.         self.messagewindow.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_ALWAYS)
  472.         self.messagewindow.add(self.messageview)
  473.         self.messageview.show()
  474.         rightwin.pack_start(self.messagewindow, True, True, 0)
  475.         self.messagewindow.show()
  476.  
  477.         messagebox = gtk.HBox(False, 4)
  478.  
  479.         self.messagecombo = gtk.combo_box_entry_new_text()
  480.         self.messagecombo.append_text("ALL")
  481.         self.messagecombo.append_text("BEACON")
  482.         self.messagecombo.append_text("CQ")
  483.         self.messagecombo.append_text("QST")
  484.         self.messagecombo.append_text("CQSRVR")
  485.         self.messagecombo.set_active(-1)
  486.         self.messagedest = self.messagecombo.get_child()
  487.         self.messagedest.set_max_length(9)
  488.         self.messagedest.set_width_chars(5)
  489.         self.messagedest.modify_font(smallfont)
  490.         messagebox.pack_start(self.messagecombo, False, False, 0)
  491.         self.messagecombo.show()
  492.  
  493.         self.messagetext = gtk.Entry()
  494.         self.messagetext.set_max_length(67)
  495.         self.messagetext.set_width_chars(31)
  496.         self.messagetext.modify_font(smallfont)
  497.         self.messagetext.connect("activate", self.send_message, self.messagetext)
  498.         messagebox.pack_start(self.messagetext, False, False, 0)
  499.         self.messagetext.show()
  500.  
  501.         messagebutton = gtk.Button()
  502.         messagebutton.set_label("Send")
  503.         messagebutton.connect("clicked", self.send_message, "message")
  504.         messagebox.pack_start(messagebutton, False, False, 0)
  505.         messagebutton.show()
  506.  
  507.         rightwin.pack_start(messagebox, False, False, 0)
  508.         messagebox.show()
  509.  
  510.         self.statusview.set_editable(False)
  511.         self.statusview.set_cursor_visible(True)
  512.         self.statusview.set_wrap_mode(gtk.WRAP_CHAR)
  513.         self.statusview.set_justification(gtk.JUSTIFY_LEFT)
  514.         self.statusview.modify_font(smallfont)
  515.  
  516.         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\nCQ\nWhen selected, sends \"CQ CQ CQ From <Station Comment>\"\nto CQSRVR every 32 minutes.\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")
  517.  
  518.         self.statuswindow = gtk.ScrolledWindow()
  519.         self.statuswindow.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_ALWAYS)
  520.         self.statuswindow.add(self.statusview)
  521.         self.statusview.show()
  522.         rightwin.pack_start(self.statuswindow, True, True, 0)
  523.         self.statuswindow.show()
  524.  
  525.         self.rawbox = gtk.HBox(False, 6)
  526.  
  527.         self.rawtext = gtk.Entry()
  528.         self.rawtext.set_max_length(128)
  529.         self.rawtext.set_width_chars(42)
  530.         self.rawtext.modify_font(smallfont)
  531.         self.rawtext.connect("activate", self.raw_send)
  532.         self.rawbox.pack_start(self.rawtext, False, False, 0)
  533.         self.rawtext.show()
  534.  
  535.         rawbutton = gtk.Button()
  536.         rawbutton.set_label("Send")
  537.         rawbutton.connect("clicked", self.raw_send)
  538.         self.rawbox.pack_start(rawbutton, False, False, 0)
  539.         rawbutton.show()
  540.  
  541.         rightwin.pack_start(self.rawbox, False, False, 0)
  542.         self.rawbox.show()
  543.  
  544.         win.pack_start(rightwin, False, False, 0)
  545.         rightwin.show()
  546.  
  547.         self.set_canvas(win)
  548.         win.show()
  549.         self.calltext.grab_focus()
  550.  
  551.         # Fix window not updating until activity after alt-tab
  552.         self.statusbuffer.create_mark("end", self.statusbuffer.get_end_iter(), False)
  553.         # Do the same for message window, without auto delete it did not have the problem
  554.         self.messagebuffer.create_mark("end", self.messagebuffer.get_end_iter(), False)
  555.  
  556.     def clear_status(self, button=None):
  557.         self.statusbuffer.set_text("")
  558.  
  559.     def clear_message(self, button=None):
  560.         self.messagebuffer.set_text("")
  561.  
  562.     def clear_message_button(self, button=None):
  563.         # cancel all outgoing messages, for now
  564.         self.clear_msg_queue()
  565.         # clear bulletin list
  566.         self.seen_bulletins = {}
  567.         # clear message screen
  568.         self.clear_message()
  569.        # TODO re-add outgoing messages (reset text iters)
  570.        # TODO re-add queued messages
  571.  
  572.     def connect_aprs(self, button):
  573.         if (self.sock == None):
  574.  
  575.             if (self.help):
  576.                 self.clear_status()
  577.                 self.messagebuffer.set_text("Message Window")
  578.                 self.messagebox = True
  579.                 self.help = False
  580.  
  581.             if (self.validate_data() == False):
  582.                 return False
  583.             self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  584.             self.status_write("Connecting ")
  585.             try:
  586.                 iplist = socket.gethostbyname_ex(HOST)[2]
  587.             except socket.error, msg:
  588.                 self.status_write("\n%s\n" % msg[1])
  589.                 self.sock = None
  590.                 return False
  591.             server = random.choice(iplist)
  592.             self.status_write("to %s\n" % server)
  593.             try:
  594.                 self.sock.connect((server, PORT))
  595.             except socket.error, msg:
  596.                 self.status_write("%s\n" % msg[1])
  597.                 self.sock = None
  598.                 return False
  599.  
  600.             self.status_write("Connected\n")
  601.             button.set_label("Disconnect")
  602.             button.modify_bg(gtk.STATE_NORMAL, gtk.gdk.color_parse("#e6000a"))
  603.        
  604.             response = self.sock.recv(RECV_BUFFER)
  605.             self.status_write("%s" % response)
  606.             if (response.find("javAPRSSrvr") == -1):
  607.                 self.status_write("invalid response.\n")
  608.                 self.disconnect_aprs(button)
  609.                 return False
  610.  
  611.             if (response.find("Port Full") != -1):
  612.                 self.status_write("Port Full.\n")
  613.                 self.disconnect_aprs(button)
  614.                 return False
  615.  
  616.             if (self.calltext.get_text() != "" and self.passtext.get_text() != ""):
  617.                 sendme = "user %s pass %s vers aprs_xo %d filter %s\n" % (self.calltext.get_text(), self.passtext.get_text(), VERSION, FILTER)
  618.             else:
  619.                 sendme = "user %s vers aprs_xo %d\n" % (self.calltext.get_text(), VERSION)
  620.             self.sock.sendall(sendme)
  621.             self.status_write("%s" % sendme)
  622.  
  623.             response = self.sock.recv(RECV_BUFFER)
  624.             self.status_write("%s" % response)
  625.             if (response.find("# logresp") == -1):
  626.                 self.status_write("invalid response.\n")
  627.                 self.disconnect_aprs(button)
  628.                 return False
  629.  
  630.             if (response.find("unverified") != -1):
  631.                 sendme = "# filter %s\n" % FILTER
  632.                 self.sock.sendall(sendme)
  633.                 self.status_write("%s\n" % sendme)
  634.  
  635.             self.status_write("\n")
  636.  
  637.             self.input_watch.append(gobject.io_add_watch(self.sock, gobject.IO_IN, self.recv_data))
  638.  
  639.             # send banner
  640.             sendme = "%s>APRS-XO v%s\n" % (self.calltext.get_text(), VERSION)
  641.             self.sock.sendall(sendme)
  642.             self.status_write("%s>\n" % self.calltext.get_text())
  643.             self.status_write("APRS-XO v%s\n\n" % VERSION)
  644.  
  645.             self.send_beacon()
  646.             self.output_watch.append(gobject.timeout_add(10 * 60 * 1000, self.send_beacon))
  647.  
  648.             # Start CQ if checked
  649.             if (self.cqbutton.get_active()):
  650.                 self.send_cq()
  651.                 self.cq_watch.append(gobject.timeout_add(32 * 60 * 1000, self.send_cq))
  652.  
  653.         else:
  654.             self.disconnect_aprs(button)
  655.  
  656.     def disconnect(self):
  657.         if (self.sock != None):
  658.             # stop cq
  659.             self.stop_cq()
  660.  
  661.             # for test server only
  662.             if(HOST == "192.168.50.6"):
  663.                 self.sock.sendall("q")
  664.  
  665.             self.sock.close()
  666.             self.sock = None
  667.  
  668.     def disconnect_aprs(self, button):
  669.         self.disconnect()
  670.         self.clear_msg_queue()
  671.  
  672.         self.status_write("Disconnected\n")
  673.  
  674.         # stop input watcher - just in case
  675.         for source in self.input_watch:
  676.             try:
  677.                 gobject.source_remove(source)
  678.             except:
  679.                 pass
  680.  
  681.         # stop beacon
  682.         for source in self.output_watch:
  683.             try:
  684.                 gobject.source_remove(source)
  685.             except:
  686.                 pass
  687.  
  688.         self.input_watch = []
  689.         self.output_watch = []
  690.  
  691.         button.set_label("Connect")
  692.         button.modify_bg(gtk.STATE_NORMAL, gtk.gdk.color_parse("#00b20d"))
  693.  
  694.     def recv_data(self, sock, condition):
  695.         if (self.sock == None):
  696.             return False
  697.         while 1:
  698.             try:
  699.                 recv_data = sock.recv(RECV_BUFFER)
  700.             except:
  701.                 self.status_write("Server closed connection.\n")
  702.                 self.sock = None
  703.                 self.disconnect_aprs(self.connectbutton)
  704.                 return False
  705.             if not recv_data:
  706.                 self.status_write("Server closed connection.\n")
  707.                 self.sock = None
  708.                 self.disconnect_aprs(self.connectbutton)
  709.                 return False
  710.             else:
  711.                 # sometimes more than one packet comes in
  712.                 for packet in recv_data.split("\n"):
  713.                     if (len(packet) < 2):
  714.                         break
  715.                     # find uri's http or www
  716.                     webaddy = packet.lower().find("http")
  717.                     if (webaddy != -1):
  718.                         packet = ("%s\n%s" % (packet[:webaddy], packet[webaddy:]))
  719.                     else:
  720.                         webaddy = packet.lower().find("www")
  721.                         if (webaddy != -1):
  722.                             packet = ("%s\n%s" % (packet[:webaddy], packet[webaddy:]))
  723.                     if (packet[0] == "#"):
  724.                         self.status_write("%s\n\n" % packet)
  725.                     else:
  726.                         cuthere = packet.find(":")
  727.                         self.status_write("%s\n" % packet[:cuthere+1])
  728.                         self.status_write("%s\n\n" % packet[cuthere+1:])
  729.                         self.msg_check(packet)
  730.                 return True
  731.  
  732.     def send_beacon(self):
  733.         if (self.sock == None):
  734.             return False
  735.         if (self.beaconbutton.get_active()):
  736.             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())
  737.             if (self.send_data(beacon)):
  738.                 return True
  739.             else:
  740.                 self.status_write("\nProblem sending beacon - STOPPED")
  741.                 return False
  742.  
  743.     def send_data(self, msg):
  744.         if (self.sock == None):
  745.             return False
  746.         path = "%s>APOLPC:" % self.calltext.get_text()
  747.         try:
  748.             self.sock.sendall("%s%s\n" % (path, msg))
  749.         except:
  750.             self.status_write("Problem sending packet\n")
  751.             self.sock = None
  752.             self.disconnect_aprs(self.connectbutton)
  753.             return False
  754.         self.status_write("%s\n" % path)
  755.         self.status_write("%s\n\n" % msg)
  756.         return True
  757.  
  758.     def send_ack(self, tocall, sequence):
  759.         # should check for duplicate messages in the next 30 seconds and drop them
  760.         # aprsd does this for us - but should do it here as well
  761.         if (tocall in self.sent_acks):
  762.             currentack = self.sent_acks[tocall]
  763.             if (currentack == sequence):
  764.                 ackmessage = ":%s:ack%s" % (tocall.ljust(9), sequence)
  765.                 self.send_data(ackmessage)
  766.         return False
  767.  
  768.     def status_write(self, text):
  769.         self.statusbuffer.insert(self.statusbuffer.get_end_iter(), text)
  770.         statuslines = self.statusbuffer.get_line_count()
  771.         if (statuslines > MAXLINES):
  772.             deletehere = self.statusbuffer.get_iter_at_line(statuslines - MAXLINES)
  773.             self.statusbuffer.delete(self.statusbuffer.get_start_iter(), deletehere)
  774.         if (not self.statusview.is_focus()):
  775.             self.statusbuffer.move_mark_by_name("end", self.statusbuffer.get_end_iter())
  776.             self.statusview.scroll_mark_onscreen(self.statusbuffer.get_mark("end"))
  777.  
  778.     def message_write(self, text, bold=None):
  779.         if (self.messagebox):
  780.             self.clear_message()
  781.             self.messagebox = False
  782.         iter = self.messagebuffer.get_end_iter()
  783.         self.messagebuffer.insert(iter, text)
  784.         if (bold):
  785.             bold_end = iter.copy()
  786.             bold_end.forward_to_line_end()
  787.             bold_start = bold_end.copy()
  788.             bold_start.backward_chars(len(text))
  789.             bold_start = bold_start.forward_search(">", gtk.TEXT_SEARCH_TEXT_ONLY)
  790.             self.messagebuffer.apply_tag(self.messagebold, bold_start[1], bold_end)
  791.  
  792.         if (not self.messageview.is_focus()):
  793.             self.messagebuffer.move_mark_by_name("end", self.messagebuffer.get_end_iter())
  794.             self.messageview.scroll_mark_onscreen(self.messagebuffer.get_mark("end"))
  795.  
  796.     def validate_data(self):
  797.         stop_here = False
  798.         self.validating = True
  799.         beaconchecked = self.beaconbutton.get_active()
  800.  
  801.         if (self.calltext.get_text() == "" and self.ziptext.get_text() == ""):
  802.             self.status_write("A callsign or zip code must be provided.\n")
  803.             return False
  804.  
  805.         # convert zip to position
  806.         if (self.ziptext.get_text() != ""):
  807.             self.status_write("Attempting to set location from zip code\n")
  808.             self.ziptext.set_text(self.ziptext.get_text().zfill(5))
  809.             try:
  810.                 zipsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  811.             except socket.error, msg:
  812.                 self.status_write("[ERROR] %s\n" % msg[1])
  813.                 self.validating = False
  814.                 return False
  815.             try:
  816.                 zipsock.connect(('geocoder.us', 80))
  817.             except socket.error, msg:
  818.                 self.status_write("[ERROR] %s\n" % msg[1])
  819.                 self.validating = False
  820.                 return False
  821.             try:
  822.                 zipsock.sendall("GET /service/csv/geocode?zip=%s\n" % self.ziptext.get_text())
  823.             except socket.error, msg:
  824.                 self.status_write("[ERROR] %s\n" % msg[1])
  825.                 self.validating = False
  826.                 return False
  827.             zipsock.shutdown(1)
  828.             try:
  829.                 response = zipsock.recv(RECV_BUFFER)
  830.             except socket.error, msg:
  831.                 self.status_write("[ERROR] %s\n" % msg[1])
  832.                 self.validating = False
  833.                 return False
  834.             A = random.randrange(0, 9)
  835.             O = random.randrange(0, 9)
  836.             if (self.calltext.get_text() == ""):
  837.                 self.calltext.set_text("X%s-%d%d" % (self.ziptext.get_text(), A, O))
  838.                 self.passtext.set_text("-1")
  839.             try:
  840.                 (lat, lon, junk) = response.split(',', 2)
  841.             except:
  842.                 self.status_write("%s\n" % response)
  843.                 lat = "0"
  844.                 lon = "0"
  845.             lat = float(lat.strip())
  846.             lon = float(lon.strip())
  847.             if (lat < 0):
  848.                 self.latcombo.set_active(1)     # S
  849.             else:
  850.                 self.latcombo.set_active(0)     # N
  851.             lat = abs(lat)
  852.             if (lon < 0):
  853.                 self.loncombo.set_active(0)     # W
  854.             else:
  855.                 self.loncombo.set_active(1)     # E
  856.             lon = abs(lon)
  857.             self.latDDtext.set_text("%02d" % int(lat))
  858.             self.latMMtext.set_text("%02d" % int((lat - int(lat)) * 60 + 0.5))
  859.             self.latmmtext.set_text("%d " % A)
  860.             self.lonDDDtext.set_text("%03d" % int(lon))
  861.             self.lonMMtext.set_text("%02d" % int((lon - int(lon)) * 60 + 0.5))
  862.             self.lonmmtext.set_text("%d " % O)
  863.             self.ziptext.set_text("")
  864.         # end set loc by zip code
  865.  
  866.         if (self.latDDtext.get_text() == "DD" or self.latDDtext.get_text() == ""):
  867.             self.status_write("Latitude Degrees are required.\n")
  868.             stop_here = True
  869.         if (self.latMMtext.get_text() == "MM" or self.latMMtext.get_text() == ""):
  870.             self.status_write("Latitude Minutes are required.\n")
  871.             stop_here = True
  872.         if (self.lonDDDtext.get_text() == "DDD" or self.lonDDDtext.get_text() == ""):
  873.             self.status_write("Longitude Degrees are required.\n")
  874.             stop_here = True
  875.         if (self.lonMMtext.get_text() == "MM" or self.lonMMtext.get_text() == ""):
  876.             self.status_write("Longitude Minutes are required.\n")
  877.             stop_here = True
  878.  
  879.         if (stop_here):
  880.             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")
  881.             self.validating = False
  882.             return False
  883.  
  884.         if (not self.latDDtext.get_text().isdigit()):
  885.             self.status_write("Latitude Degrees must be a number.\n")
  886.             stop_here = True
  887.         if (not self.latMMtext.get_text().isdigit()):
  888.             self.status_write("Latitude Minutes must be a number.\n")
  889.             stop_here = True
  890.         if (not self.lonDDDtext.get_text().isdigit()):
  891.             self.status_write("Longitude Degrees must be a number.\n")
  892.             stop_here = True
  893.         if (not self.lonMMtext.get_text().isdigit()):
  894.             self.status_write("Longitude Minutes must be a number.\n")
  895.             stop_here = True
  896.  
  897.         if (stop_here):
  898.             self.status_write("Invalid Position.\n")
  899.             self.validating = False
  900.             return False
  901.  
  902.         if (int(self.latDDtext.get_text()) < 0  or int(self.latDDtext.get_text()) > 90):
  903.             self.status_write("Latitude Degrees must be between 0 and 90.\n")
  904.             stop_here = True
  905.         if (int(self.latMMtext.get_text()) < 0  or int(self.latMMtext.get_text()) > 60):
  906.             self.status_write("Latitude Minutes must be between 0 and 60.\n")
  907.             stop_here = True
  908.         if (int(self.lonDDDtext.get_text()) < 0  or int(self.lonDDDtext.get_text()) > 180):
  909.             self.status_write("Longitude Degrees must be between 0 and 180.\n")
  910.             stop_here = True
  911.         if (int(self.lonMMtext.get_text()) < 0  or int(self.lonMMtext.get_text()) > 60):
  912.             self.status_write("Longitude Minutes must be between 0 and 60.\n")
  913.             stop_here = True
  914.  
  915.         if (stop_here):
  916.             self.status_write("Invalid Position.\n")
  917.             self.validating = False
  918.             return False
  919.  
  920.         # clean up entries
  921.         self.latDDtext.set_text(self.latDDtext.get_text().zfill(2))
  922.         self.latMMtext.set_text(self.latMMtext.get_text().zfill(2))
  923.         self.lonDDDtext.set_text(self.lonDDDtext.get_text().zfill(3))
  924.         self.lonMMtext.set_text(self.lonMMtext.get_text().zfill(2))
  925.         self.latmmtext.set_text(self.latmmtext.get_text().ljust(2))
  926.         self.lonmmtext.set_text(self.lonmmtext.get_text().ljust(2))
  927.         if (self.latmmtext.get_text() == "mm"):
  928.             self.latmmtext.set_text("  ")
  929.         if (self.lonmmtext.get_text() == "mm"):
  930.             self.lonmmtext.set_text("  ")
  931.         self.calltext.set_text(self.calltext.get_text().upper())
  932.         if (self.passtext.get_text() == ""):
  933.             self.passtext.set_text(self.aprspass(self.calltext.get_text()))
  934.         self.beaconbutton.set_active(beaconchecked)
  935.         self.validating = False
  936.  
  937.     def can_close( self ):
  938.         self.hide()
  939.         if (self.sock != None):
  940.             self.disconnect()
  941.         return True
  942.  
  943.     def write_file(self, file_path):
  944.         try:
  945.             JournalData = {}
  946.             JournalData['callsign'] = self.calltext.get_text()
  947.             JournalData['password'] = self.passtext.get_text()
  948.             JournalData['latDD'] = self.latDDtext.get_text()
  949.             JournalData['latMM'] = self.latMMtext.get_text()
  950.             JournalData['latmm'] = self.latmmtext.get_text()
  951.             JournalData['lat'] = self.latcombo.get_active_text()
  952.             JournalData['lonDDD'] = self.lonDDDtext.get_text()
  953.             JournalData['lonMM'] = self.lonMMtext.get_text()
  954.             JournalData['lonmm'] = self.lonmmtext.get_text()
  955.             JournalData['lon'] = self.loncombo.get_active_text()
  956.             JournalData['stationtext'] = self.stationtext.get_text()
  957.             if (self.beaconbutton.get_active()):
  958.                 JournalData['beacon'] = 'True'
  959.             else:
  960.                 JournalData['beacon'] = 'False'
  961.             if (self.passbutton.get_active()):
  962.                 JournalData['hidepass'] = 'True'
  963.             else:
  964.                 JournalData['hidepass'] = 'False'
  965.             if (self.cqbutton.get_active()):
  966.                 JournalData['cq'] = 'True'
  967.             else:
  968.                 JournalData['cq'] = 'False'
  969.  
  970.             callsignlist = []
  971.             model = self.messagecombo.get_model()
  972.             iter = model.get_iter_first()
  973.             while iter:
  974.                 callsignlist.append(model.get(iter, 0)[0])
  975.                 iter = model.iter_next(iter)
  976.  
  977.             JournalData['callsignlist'] = callsignlist
  978.             if (self.help):
  979.                 JournalData['messages'] = "Message Window"
  980.             else:
  981.                 JournalData['messages'] = self.messagebuffer.get_text(self.messagebuffer.get_start_iter(), self.messagebuffer.get_end_iter())
  982.             data = json.write(JournalData)
  983.  
  984.             f = open(file_path, 'w')
  985.             try:
  986.                 f.write(data)
  987.             finally:
  988.                 f.close()
  989.  
  990.         except Exception, e:
  991.             self.status_write("write_file(): %s\n" % e)
  992.  
  993.     def read_file(self, file_path):
  994.         self.statusbuffer.set_text("Status Window\n\n")
  995.         JournalData = {}
  996.         callsignlist = []
  997.         messages = "Message Window"
  998.         callsign = ""
  999.         password = ""
  1000.         latDD = "DD"
  1001.         latMM = "MM"
  1002.         latmm = "mm"
  1003.         lat = "N"
  1004.         lonDDD = "DDD"
  1005.         lonMM = "MM"
  1006.         lonmm = "mm"
  1007.         lon = "W"
  1008.         stationtext = ""
  1009.         beacon = "True"
  1010.         hidepass = "True"
  1011.         cq = "False"
  1012.         try:
  1013.             f = open(file_path, 'r')
  1014.             JournalData = json.read(f.read())
  1015.             if JournalData.has_key('callsignlist'):
  1016.                 callsignlist = JournalData['callsignlist']
  1017.             if JournalData.has_key('messages'):
  1018.                 messages = JournalData['messages']
  1019.             if JournalData.has_key('callsign'):
  1020.                 callsign = JournalData['callsign']
  1021.             if JournalData.has_key('password'):
  1022.                 password = JournalData['password']
  1023.             if JournalData.has_key('latDD'):
  1024.                 latDD = JournalData['latDD']
  1025.             if JournalData.has_key('latMM'):
  1026.                 latMM = JournalData['latMM']
  1027.             if JournalData.has_key('latmm'):
  1028.                 latmm = JournalData['latmm']
  1029.             if JournalData.has_key('lat'):
  1030.                 lat = JournalData['lat']
  1031.             if JournalData.has_key('lonDDD'):
  1032.                 lonDDD = JournalData['lonDDD']
  1033.             if JournalData.has_key('lonMM'):
  1034.                 lonMM = JournalData['lonMM']
  1035.             if JournalData.has_key('lonmm'):
  1036.                 lonmm = JournalData['lonmm']
  1037.             if JournalData.has_key('lon'):
  1038.                 lon = JournalData['lon']
  1039.             if JournalData.has_key('stationtext'):
  1040.                 stationtext = JournalData['stationtext']
  1041.             if JournalData.has_key('beacon'):
  1042.                 beacon = JournalData['beacon']
  1043.             if JournalData.has_key('hidepass'):
  1044.                 hidepass = JournalData['hidepass']
  1045.             if JournalData.has_key('cq'):
  1046.                 cq = JournalData['cq']
  1047.         except:
  1048.             pass
  1049.         finally:
  1050.             f.close()
  1051. #        self.messagebuffer.set_text(messages)
  1052.         self.bold_messages(messages)
  1053.         self.help = False
  1054.         if (messages == "Message Window"):
  1055.             self.messagebox = True
  1056.         else:
  1057.             self.messagebox = False
  1058.         for currentcall in callsignlist:
  1059.             self.add_callsign(currentcall, False)
  1060.         self.calltext.set_text(callsign)
  1061.         self.passtext.set_text(password)
  1062.         self.latDDtext.set_text(latDD)
  1063.         self.latMMtext.set_text(latMM)
  1064.         self.latmmtext.set_text(latmm)
  1065.         if (lat == "N"):
  1066.             self.latcombo.set_active(0)
  1067.         else:
  1068.             self.latcombo.set_active(1)
  1069.         self.lonDDDtext.set_text(lonDDD)
  1070.         self.lonMMtext.set_text(lonMM)
  1071.         self.lonmmtext.set_text(lonmm)
  1072.         if (lon == "W"):
  1073.             self.loncombo.set_active(0)
  1074.         else:
  1075.             self.loncombo.set_active(1)
  1076.         if (stationtext == ""):
  1077.             firstName = profile.get_nick_name().split(None, 1)[0].capitalize()
  1078.             self.stationtext.set_text("%s's XO at home." % firstName)
  1079.         else:
  1080.             self.stationtext.set_text(stationtext)
  1081.         if (beacon == "True"):
  1082.             self.beaconbutton.set_active(True)
  1083.         else:
  1084.             self.beaconbutton.set_active(False)
  1085.         if (hidepass == "True"):
  1086.             self.passbutton.set_active(True)
  1087.         else:
  1088.             self.passbutton.set_active(False)
  1089.         if (cq == "True"):
  1090.             self.cqbutton.set_active(True)
  1091.         else:
  1092.             self.cqbutton.set_active(False)
  1093.  
  1094.     def msg_check(self, data):
  1095.         mycall = self.calltext.get_text().upper()
  1096.         firstcheck = data.find("::")
  1097.         secondcheck = data[firstcheck+11:firstcheck+12]
  1098.         thirdcheck = data.find("{")
  1099.         if (firstcheck != -1 and secondcheck == ":"):
  1100.             tocall = data[firstcheck+2:firstcheck+11].upper()
  1101.             strippedtocall = tocall.strip()
  1102.             fromcall = data[:data.find(">")].upper()
  1103.             isbulletin = self.bulletin_check(strippedtocall)
  1104.             # 'mycall' check allows people with bulletin looking calls to ack messages
  1105.             # this check does not fix the problem of sending messages to bulletin looking calls
  1106.             if (isbulletin and strippedtocall != mycall):
  1107.                 message = data[firstcheck+12:]
  1108.                 self.add_callsign(fromcall, False)
  1109.                 bln_id = "%s-%s" % (fromcall, strippedtocall)
  1110.                 # show bulletin if have never seen it or has been at least 1 day since last viewing
  1111.                 if (not bln_id in self.seen_bulletins or time.time() - self.seen_bulletins[bln_id] > 86400):
  1112.                     self.seen_bulletins[bln_id] = time.time()
  1113.                     self.message_write("%s %s:%s> %s\n" % (time.strftime("%m/%d %H:%M", time.localtime()), fromcall, strippedtocall, message), True)
  1114.             else:
  1115.                 if (strippedtocall == mycall):
  1116.                     self.add_callsign(fromcall, False)
  1117.                     if (thirdcheck != -1):
  1118.                         sequence_end = data.find("}")
  1119.                         if (sequence_end == -1):
  1120.                             sequence = data[thirdcheck + 1:]
  1121.                         else:
  1122.                             sequence = data[thirdcheck + 1:sequence_end]
  1123.                             replyack = data[sequence_end + 1:]
  1124.                             if (len(replyack) > 1):
  1125.                                 ackid = "%s-%s" % (fromcall, replyack)
  1126.                                 if (ackid in self.recv_acks):
  1127.                                     if (self.recv_acks[ackid] == 0):
  1128.  
  1129.                                         count_start = self.messagebuffer.get_iter_at_mark(self.message_marks[ackid])
  1130.                                         count_end = count_start.forward_search(">", gtk.TEXT_SEARCH_TEXT_ONLY)
  1131.                                         self.messagebuffer.delete(count_start, count_end[0])
  1132.  
  1133.                                         line_end = self.messagebuffer.get_iter_at_mark(self.message_marks[ackid])
  1134.                                         queue_start = self.messagebuffer.get_iter_at_mark(self.message_marks[ackid])
  1135.                                         line_end.forward_to_line_end()
  1136.                                         queue_start.forward_to_line_end()
  1137.                                         queue_start.backward_chars(12)
  1138.                                         self.messagebuffer.delete(queue_start, line_end)
  1139.  
  1140.                                         line_end = self.messagebuffer.get_iter_at_mark(self.message_marks[ackid])
  1141.                                         line_end.forward_to_line_end()
  1142.                                         self.messagebuffer.insert(line_end, " <*ACKED*>")
  1143.  
  1144.                                         self.send_msg_queue(fromcall)
  1145.                                     self.recv_acks[ackid] = 1
  1146.                         message = data[firstcheck+12:thirdcheck]
  1147.                         ackmessage = ":%s:ack%s" % (fromcall.ljust(9), data[thirdcheck + 1:])
  1148.                         self.send_data(ackmessage)
  1149.                         id = "%s-%s" % (fromcall, sequence)
  1150.                         if (id in self.sent_acks):
  1151.                             self.timers.append(gobject.timeout_add(30 * 1000, self.send_ack, tocall, sequence))
  1152.                             self.timers.append(gobject.timeout_add(60 * 1000, self.send_ack, tocall, sequence))
  1153.                             self.timers.append(gobject.timeout_add(120 * 1000, self.send_ack, tocall, sequence))
  1154.                         else:
  1155.                             # TODO beep?
  1156.                             self.message_write("%s %s> %s\n" % (time.strftime("%m/%d %H:%M", time.localtime()), fromcall, message), True)
  1157.                         self.sent_acks[id] = time.time() # to help a cleanup thread later
  1158.                         self.sent_acks[fromcall] = sequence # to help with reply acks later
  1159.                     else:
  1160.                         message = data[firstcheck+12:]
  1161.                         if (message[0:3] == "ack"):
  1162.                             sequence_end = message.find("}")
  1163.                             if (sequence_end == -1):
  1164.                                 sequence = message[3:]
  1165.                             else:
  1166.                                 sequence = message[3:sequence_end]
  1167.                             ackid = "%s-%s" % (fromcall, sequence)
  1168.                             if (ackid in self.recv_acks):
  1169.                                 if (self.recv_acks[ackid] == 0):
  1170.  
  1171.                                     count_start = self.messagebuffer.get_iter_at_mark(self.message_marks[ackid])
  1172.                                     count_end = count_start.forward_search(">", gtk.TEXT_SEARCH_TEXT_ONLY)
  1173.                                     self.messagebuffer.delete(count_start, count_end[0])
  1174.  
  1175.                                     line_end = self.messagebuffer.get_iter_at_mark(self.message_marks[ackid])
  1176.                                     queue_start = self.messagebuffer.get_iter_at_mark(self.message_marks[ackid])
  1177.                                     line_end.forward_to_line_end()
  1178.                                     queue_start.forward_to_line_end()
  1179.                                     queue_start.backward_chars(12)
  1180.                                     self.messagebuffer.delete(queue_start, line_end)
  1181.  
  1182.                                     line_end = self.messagebuffer.get_iter_at_mark(self.message_marks[ackid])
  1183.                                     line_end.forward_to_line_end()
  1184.                                     self.messagebuffer.insert(line_end, " <*ACKED*>")
  1185.  
  1186.                                     self.send_msg_queue(fromcall)
  1187.                             self.recv_acks[ackid] = 1
  1188.                         else:
  1189.                             # TODO beep?
  1190.                             self.message_write("%s %s> %s\n" % (time.strftime("%m/%d %H:%M", time.localtime()), fromcall, message), True)
  1191.  
  1192.                             # TODO query check
  1193.                             if (message[0] == "?" and message[-1] == "?"):
  1194.                                 pass
  1195.  
  1196.  
  1197.     def disable_beacon(self, widget, data=None):
  1198.         if (self.sock != None):
  1199.             self.beaconbutton.set_active(False)
  1200.  
  1201.     def enable_beacon(self, widget, data=None):
  1202.         if (not self.validating and widget.get_active()):
  1203.             self.validate_data()
  1204.  
  1205.     def raw_send(self, widget, data=None):
  1206.         msg = "%s\n" % self.rawtext.get_text()
  1207.         try:
  1208.             self.sock.sendall(msg)
  1209.         except:
  1210.             self.status_write("Problem sending message\n")
  1211.             self.sock = None
  1212.             self.disconnect_aprs(self.connectbutton)
  1213.             return False
  1214.         self.status_write("%s\n" % msg)
  1215.         self.rawtext.set_text("")
  1216.         return True
  1217.  
  1218.     def send_message(self, widget, data=None, tocall=None, message=None):
  1219.         sendnow = False
  1220.  
  1221.         if (tocall == None and message == None):
  1222.             tocall = self.messagedest.get_text().upper()
  1223.             message = self.messagetext.get_text()
  1224.  
  1225.         if (message == ""):
  1226.             return False
  1227.  
  1228.         isbulletin = self.bulletin_check(tocall)
  1229.  
  1230.         # check for illegal characters before clearing message
  1231.         if (isbulletin):
  1232.             if (message.find("|") != -1 or message.find("~") != -1):
  1233.                 self.message_write("Bulletins can not contain the following characters: | ~")
  1234.                 return False
  1235.         else:
  1236.             if (message.find("|") != -1 or message.find("~") != -1 or message.find("{") != -1):
  1237.                 self.message_write("Messages can not contain the following characters: | ~ {")
  1238.                 return False
  1239.  
  1240.         # add callsign to list if just entered
  1241.         if (self.messagecombo.get_active() == -1):
  1242.             self.add_callsign(tocall, True)
  1243.  
  1244.         # get a sequence number
  1245.         if (isbulletin):
  1246.             # don't waste a number on a bulletin
  1247.             sequence = ""
  1248.         else:
  1249.             sequence = "%s" % self.b90()
  1250.  
  1251.         # TODO
  1252.         # cancel message option - menu popup
  1253.  
  1254.         # is there a queue for this callsign?
  1255.         if (tocall not in self.queue_list):
  1256.             sendnow = True
  1257.  
  1258.         # add the message to the queue
  1259.         if (not self.msg_queue(tocall, sequence, message)):
  1260.             if (self.sequence > 0):
  1261.                 self.sequence -= 1
  1262.             return False
  1263.  
  1264.         # clear the message text box
  1265.         self.messagetext.set_text("")
  1266.  
  1267.         if (sendnow):
  1268.             self.send_msg_queue(tocall)
  1269.  
  1270.     def b90(self):
  1271.         if (self.sequence > 8099):
  1272.             self.sequence = 0
  1273.         first = ((self.sequence / 90) % 90) + 33
  1274.         second = (self.sequence % 90) + 33
  1275.         output = "%c%c" % (first, second)
  1276.         self.sequence += 1
  1277.         return output
  1278.  
  1279.     def msg_timer(self, tocall, message, sequence, count, delay):
  1280.         id = "%s-%s" % (tocall, sequence)
  1281.         if (self.recv_acks[id] == 1):
  1282.             return False
  1283.         else:
  1284.             replyack = self.replyack(tocall)
  1285.             self.send_data(":%s:%s{%s}%s" % (tocall.ljust(9), message, sequence, replyack))
  1286.  
  1287.             count_start = self.messagebuffer.get_iter_at_mark(self.message_marks[id])
  1288.             count_end = self.messagebuffer.get_iter_at_mark(self.message_marks[id])
  1289.             count_end.forward_chars(len(str(count - 1)) + 1)
  1290.             self.messagebuffer.delete(count_start, count_end)
  1291.             iter = self.messagebuffer.get_iter_at_mark(self.message_marks[id])
  1292.             self.messagebuffer.insert(iter, " %i" % count)
  1293.  
  1294.             # start the next timer
  1295.             count += 1
  1296.             if (count > MAXRETRIES):
  1297.                 self.recv_acks[id] = 1
  1298.  
  1299.                 count_start = self.messagebuffer.get_iter_at_mark(self.message_marks[id])
  1300.                 count_end = self.messagebuffer.get_iter_at_mark(self.message_marks[id])
  1301.                 count_end.forward_chars(len(str(count - 1)) + len(str(MAXRETRIES)) + 2)
  1302.                 self.messagebuffer.delete(count_start, count_end)
  1303.  
  1304.                 line_end = self.messagebuffer.get_iter_at_mark(self.message_marks[id])
  1305.                 queue_start = self.messagebuffer.get_iter_at_mark(self.message_marks[id])
  1306.                 line_end.forward_to_line_end()
  1307.                 queue_start.forward_to_line_end()
  1308.                 queue_start.backward_chars(12)
  1309.                 self.messagebuffer.delete(queue_start, line_end)
  1310.  
  1311.                 line_end = self.messagebuffer.get_iter_at_mark(self.message_marks[id])
  1312.                 line_end.forward_to_line_end()
  1313.                 self.messagebuffer.insert(line_end, " <-timeout->")
  1314.  
  1315.                 self.send_msg_queue(tocall)
  1316.                 return False
  1317.             delay *=  2
  1318.             if (delay > 600):
  1319.                 delay = 600
  1320.             gobject.timeout_add(delay * 1000, self.msg_timer, tocall, message, sequence, count, delay)
  1321.  
  1322.             # and stop this timer
  1323.             return False
  1324.  
  1325.     def add_callsign(self, callsign, activate):
  1326.         model = self.messagecombo.get_model()
  1327.         notfound = True
  1328.         iter = model.get_iter_first()
  1329.         while iter:
  1330.             currentcall = model.get(iter, 0)[0]
  1331.             if (currentcall == callsign):
  1332.                 notfound = False
  1333.                 break
  1334.             iter = model.iter_next(iter)
  1335.         if (notfound):
  1336.             self.messagecombo.prepend_text(callsign)
  1337.             if (activate):
  1338.                 self.messagecombo.set_active(0)
  1339.  
  1340.     def bulletin_check(self, callsign):
  1341.         # hard code CQSRVR
  1342.         if (callsign == "CQSRVR"):
  1343.             return False
  1344.         for currentcall in self.bulletins:
  1345.             length = len(currentcall)
  1346.             if (currentcall == callsign[:length]):
  1347.                 return True
  1348.         return False
  1349.  
  1350.     def replyack(self, tocall):
  1351.         replyack = ""
  1352.         if (tocall in self.sent_acks):
  1353.             id = "%s-%s" % (tocall, self.sent_acks[tocall])
  1354.             if (time.time() - self.sent_acks[id] < 5400):
  1355.                 # less than 90 minutes ago
  1356.                 replyack = self.sent_acks[tocall]
  1357.         return replyack
  1358.  
  1359.     def msg_queue(self, call, sequence, message):
  1360.         if (len(self.message_list) >= MAX_MSG_QUEUE):
  1361.             self.message_write("Too many messages in queue.\n")
  1362.             return False
  1363.         id = "%s-%s" % (call, sequence)
  1364.         if (call in self.queue_list):
  1365.             if (len(self.queue_list[call]) >= MAX_PER_CALL_QUEUE):
  1366.                 self.message_write("Too many messages to %s in queue.\n" % call)
  1367.                 return False
  1368.             self.queue_list[call].append(sequence)
  1369.             self.message_list[id] = message
  1370.         else:
  1371.             self.queue_list[call] = []
  1372.             self.queue_list[call].append(sequence)
  1373.             self.message_list[id] = message
  1374.         self.message_write("%s To:%s> %s <-queued->\n" % (time.strftime("%m/%d %H:%M", time.localtime()), call, message))
  1375.         # if a message is received before the next lines are run there will be problems...
  1376.         iter = self.messagebuffer.get_iter_at_line(self.messagebuffer.get_line_count() - 2)
  1377.         iter.forward_chars(15 + len(call))
  1378.         self.message_marks[id] = self.messagebuffer.create_mark(None, iter, True)
  1379. #        self.message_marks[id].set_visible(True)
  1380.  
  1381.         bold_end = iter.copy()
  1382.         bold_start = iter.copy()
  1383.         bold_end.forward_to_line_end()
  1384.         bold_end = bold_end.backward_search("<", gtk.TEXT_SEARCH_TEXT_ONLY)
  1385.         bold_start.forward_chars(2)
  1386.         self.messagebuffer.apply_tag(self.messagebold, bold_start, bold_end[0])
  1387.  
  1388.         return True
  1389.  
  1390.     def send_msg_queue(self, call):
  1391.         if (call in self.queue_list):
  1392.             if (self.queue_list[call] == []):
  1393.                 del self.queue_list[call]
  1394.                 return False
  1395.  
  1396.             sequence = self.queue_list[call].pop(0)
  1397.             id = "%s-%s" % (call, sequence)
  1398.             message = self.message_list[id]
  1399.             del self.message_list[id]
  1400.  
  1401.             # record this so cancel can work on current messages
  1402.             self.current_message[call] = sequence
  1403.  
  1404.             # send message
  1405.             isbulletin = self.bulletin_check(call)
  1406.             if (isbulletin):
  1407.                 self.send_data(":%s:%s" % (call.ljust(9), message))
  1408.                 gobject.timeout_add(600 * 1000, self.msg_timer, call, message, "", 2, 600)
  1409.             else:
  1410.                 replyack = self.replyack(call)
  1411.                 self.send_data(":%s:%s{%s}%s" % (call.ljust(9), message, sequence, replyack))
  1412.                 self.recv_acks["%s-%s" % (call, sequence)] = 0
  1413.                 gobject.timeout_add(7 * 1000, self.msg_timer, call, message, sequence, 2, 7)
  1414.  
  1415.             # reset the timestamp
  1416.             line_start = self.messagebuffer.get_iter_at_mark(self.message_marks[id])
  1417.             date_end = self.messagebuffer.get_iter_at_mark(self.message_marks[id])
  1418.             length = line_start.get_line_offset()
  1419.             line_start.backward_chars(length)
  1420.             date_end.backward_chars(length)
  1421.             date_end.forward_chars(11)
  1422.             self.messagebuffer.delete(line_start, date_end)
  1423.  
  1424.             line_start = self.messagebuffer.get_iter_at_mark(self.message_marks[id])
  1425.             length = line_start.get_line_offset()
  1426.             line_start.backward_chars(length)
  1427.             self.messagebuffer.insert(line_start, time.strftime("%m/%d %H:%M", time.localtime()))
  1428.  
  1429.             # clear "<-queued->"
  1430.             line_end = self.messagebuffer.get_iter_at_mark(self.message_marks[id])
  1431.             queue_start = self.messagebuffer.get_iter_at_mark(self.message_marks[id])
  1432.             line_end.forward_to_line_end()
  1433.             queue_start.forward_to_line_end()
  1434.             queue_start.backward_chars(11)
  1435.             self.messagebuffer.delete(queue_start, line_end)
  1436.  
  1437.             # add <-sending->
  1438.             line_end = self.messagebuffer.get_iter_at_mark(self.message_marks[id])
  1439.             line_end.forward_to_line_end()
  1440.             self.messagebuffer.insert(line_end, " <-sending->")
  1441.  
  1442.             # add the counter
  1443.             iter = self.messagebuffer.get_iter_at_mark(self.message_marks[id])
  1444.             self.messagebuffer.insert(iter, " 1/%s" % MAXRETRIES)
  1445.  
  1446.     def cancel_message(self, id):
  1447.         if (self.recv_acks[id] == 1):
  1448.             return
  1449.  
  1450.         self.recv_acks[id] = 1
  1451.  
  1452.         # remove status
  1453.         line_end = self.messagebuffer.get_iter_at_mark(self.message_marks[id])
  1454.         line_end.forward_to_line_end()
  1455.         status_start = line_end.backward_search("<", gtk.TEXT_SEARCH_TEXT_ONLY)
  1456.         self.messagebuffer.delete(status_start[0], line_end)
  1457.  
  1458.         # add <-cancelled->
  1459.         line_end = self.messagebuffer.get_iter_at_mark(self.message_marks[id])
  1460.         line_end.forward_to_line_end()
  1461.         self.messagebuffer.insert(line_end, " <-cancelled->")
  1462.  
  1463.         # leave counter and timestamp alone
  1464.  
  1465.     def clear_msg_queue(self, button=None):
  1466.         if (self.queue_list != {}):
  1467.             for call in self.queue_list:
  1468.                 for sequence in self.queue_list[call]:
  1469.                     id = "%s-%s" % (call, sequence)
  1470.                     del self.message_list[id]
  1471.                     self.cancel_message(id)
  1472.             self.queue_list = {}
  1473.  
  1474.         if (self.current_message != {}):
  1475.             for call in self.current_message:
  1476.                 sequence = self.current_message[call]
  1477.                 id = "%s-%s" % (call, sequence)
  1478.                 self.cancel_message(id)
  1479.             self.current_message = {}
  1480.  
  1481.     def bold_messages(self, messages):
  1482.         self.messagebuffer.set_text("")
  1483.         for line in messages.splitlines():
  1484.             if (line[2:3] == "/" and line[5:6] == " " and line[8:9] == ":" and line[11:12] == " "):
  1485.                 if (line[12:15] == "To:"):
  1486.                     self.message_write("%s\n" % line)
  1487.                     iter = self.messagebuffer.get_iter_at_line(self.messagebuffer.get_line_count() - 2)
  1488.                     bold_end = iter.copy()
  1489.                     bold_end.forward_to_line_end()
  1490.                     bold_start = bold_end.copy()
  1491.                     bold_start.backward_chars(len(line))
  1492.                     bold_start = bold_start.forward_search(">", gtk.TEXT_SEARCH_TEXT_ONLY)
  1493.                     bold_end = bold_end.backward_search("<", gtk.TEXT_SEARCH_TEXT_ONLY)
  1494.                     self.messagebuffer.apply_tag(self.messagebold, bold_start[1], bold_end[0])
  1495.                 else:
  1496.                     self.message_write("%s\n" % line, True)
  1497.             else:
  1498.                 self.message_write("%s\n" % line)
  1499.  
  1500.     def aprspass(self, callsign):
  1501.         # Note: The doHash(char*) function is Copyright Steve Dimse 1998
  1502.         # As of April 11 2000 Steve Dimse has released this code to the open source aprs community
  1503.  
  1504.         # remove SSID, trim callsign, convert to upper case
  1505.         cuthere = callsign.find("-")
  1506.         if (cuthere != -1):
  1507.             callsign = callsign[:cuthere]
  1508.         realcall = callsign[:10].upper()
  1509.  
  1510.         if (realcall == "NOCALL"):
  1511.             return "-1"
  1512.  
  1513.         # initialize hash
  1514.         hash = 0x73e2
  1515.         i = 0
  1516.         length = len(realcall)
  1517.  
  1518.         # hash callsign two bytes at a time
  1519.         while (i < length):
  1520.             hash ^= ord(realcall[i])<<8
  1521.             if (i+1 < length):
  1522.                 hash ^= ord(realcall[i+1])
  1523.             i += 2
  1524.  
  1525.         # convert to string and mask off the high bit so number is always positive
  1526.         return str(hash & 0x7fff)
  1527.  
  1528.     def hide_password(self, widget, data=None):
  1529.         if (widget.get_active()):
  1530.             self.passtext.set_visibility(False)
  1531.         else:
  1532.             self.passtext.set_visibility(True)
  1533.  
  1534.     def open_url_button(self, widget, data):
  1535.         self._show_via_journal(data)
  1536.  
  1537.     # (mostly) from Chat.Activity
  1538.     def _show_via_journal(self, url):
  1539.         """Ask the journal to display a URL"""
  1540.         import os
  1541.         import time
  1542.         from sugar import profile
  1543.         from sugar.activity.activity import show_object_in_journal
  1544.         from sugar.datastore import datastore
  1545.         jobject = datastore.create()
  1546.         metadata = {
  1547.             'title': url,
  1548.             'title_set_by_user': '1',
  1549.             'icon-color': profile.get_color().to_string(),
  1550.             'activity': 'org.laptop.WebActivity',
  1551.             'mime_type': 'text/plain',
  1552.             }
  1553.         for k,v in metadata.items():
  1554.             jobject.metadata[k] = v
  1555.         file_path = os.path.join(self.get_activity_root(), 'instance',
  1556.                                  '%i_' % time.time())
  1557.         open(file_path, 'w').write('{"deleted":[],"shared_links":[],"history":[{"url":"' + url + '","title":"' + url + '"}]}')
  1558.         os.chmod(file_path, 0755)
  1559.         jobject.set_file_path(file_path)
  1560.         datastore.write(jobject)
  1561.         show_object_in_journal(jobject.object_id)
  1562.         jobject.destroy()
  1563.         os.unlink(file_path)
  1564.  
  1565.     def stop_cq(self):
  1566.         for source in self.cq_watch:
  1567.             try:
  1568.                 gobject.source_remove(source)
  1569.             except:
  1570.                 pass
  1571.         self.cq_watch = []
  1572.         if (self.cqbutton.get_active()):
  1573.             # send_message fails because disconnect happens too fast.
  1574. #            self.send_message(None, None, "CQSRVR", "U CQ")
  1575.             message = ":CQSRVR   :U CQ{%s" % self.b90()
  1576.             self.send_data(message)
  1577.  
  1578.     def enable_cq(self, widget, data=None):
  1579.         if (self.sock != None):
  1580.             if (self.cqbutton.get_active()):
  1581.                 self.send_cq()
  1582.                 self.cq_watch.append(gobject.timeout_add(32 * 60 * 1000, self.send_cq))
  1583.             else:
  1584.                 self.stop_cq()
  1585.                 self.send_message(None, None, "CQSRVR", "U CQ")
  1586.  
  1587.     def send_cq(self):
  1588.         if (self.sock == None):
  1589.             return False
  1590.         if (self.cqbutton.get_active()):
  1591.             self.send_message(None, None, "CQSRVR", "CQ CQ CQ From %s" % self.stationtext.get_text())
  1592.  
  1593.