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