From 33bc81210c2bc293c79212755a49eb2432ef8ade Mon Sep 17 00:00:00 2001 From: Jack Zielke Date: Sun, 31 May 2009 00:39:17 +0000 Subject: May 30 2009 - version 14 - cancel individual messages, auto-complete tocall, yahoo zip codes --- diff --git a/NEWS b/NEWS index c0a9454..f27bf90 100644 --- a/NEWS +++ b/NEWS @@ -11,4 +11,5 @@ November 9 2008 - version 9 - journal works, message text in bold, scroll bugfix November 22 2008 - version 10 - changed journal, old messages are bolded on load December 29 2008 - version 11 - auto generate passwords, better bolding (NWS messages) May 18 2009 - version 12 - cqsrvr, show new bulletins, better URIs, clear button -May 19 2008 - version 13 - updated links, help, activity.info +May 19 2009 - version 13 - updated links, help, activity.info +May 30 2009 - version 14 - cancel individual messages, auto-complete tocall, yahoo zip codes diff --git a/TODO b/TODO index b773095..5f540a2 100644 --- a/TODO +++ b/TODO @@ -1,8 +1,5 @@ multi language -cancel individual messages -retry messages - beep on message receive cleanup timers on disconnect @@ -11,3 +8,6 @@ cleanup old timers in self.timers[] auto reconnect? aprs.laptop.org? + +color coded status window +gps diff --git a/activity/activity.info b/activity/activity.info index 7f725db..efa4d91 100644 --- a/activity/activity.info +++ b/activity/activity.info @@ -1,6 +1,6 @@ [Activity] name = APRS-XO -activity_version = 13 +activity_version = 14 bundle_id = org.laptop.APRSXO service_name = org.laptop.APRSXO class = aprs.APRSActivity diff --git a/aprs.py b/aprs.py index 849ccce..8638d4a 100755 --- a/aprs.py +++ b/aprs.py @@ -31,9 +31,10 @@ from sugar.activity import activity from sugar.bundle.activitybundle import ActivityBundle from sugar.graphics.menuitem import MenuItem from sugar.graphics.toolbutton import ToolButton +from xml.dom.minidom import parseString HOST = 'rotate.aprs2.net' -#HOST = '192.168.50.6' +#HOST = '192.168.50.14' PORT = 14580 RECV_BUFFER = 4096 @@ -71,6 +72,10 @@ class APRSActivity(activity.Activity): self.output_watch = [] self.cq_watch = [] self.current_message = {} + self.current_message_text = {} + self.current_message_count = {} + self.current_message_delay = {} + self.last_selected = '' titlefont = pango.FontDescription('Sans bold 8') mediumfont = pango.FontDescription('Sans 6.5') @@ -198,7 +203,7 @@ class APRSActivity(activity.Activity): self.passbutton = gtk.CheckButton() self.passbutton.set_active(True) - self.passbutton.connect("toggled", self.hide_password, "password") + self.passbutton.connect("toggled", self.hide_password) bottomidentbox.pack_start(self.passbutton, False, False, 0) self.passbutton.show() @@ -434,23 +439,27 @@ class APRSActivity(activity.Activity): rightwintopbox = gtk.HBox(False, 4) clearbutton = gtk.Button() -# clearbutton.set_label(" Cancel Messages ") -# clearbutton.connect("clicked", self.clear_msg_queue) - clearbutton.set_label(" Clear/Cancel ") + clearbutton.set_label(" Clear ") clearbutton.connect("clicked", self.clear_message_button) - rightwintopbox.pack_start(clearbutton, False, False, 5) + rightwintopbox.pack_start(clearbutton, False, False, 1) clearbutton.show() + cancelbutton = gtk.Button() + cancelbutton.set_label(" Cancel ") + cancelbutton.connect("clicked", self.cancel_dialog) + rightwintopbox.pack_start(cancelbutton, False, False, 1) + cancelbutton.show() + self.cqbutton = gtk.CheckButton("CQ") self.cqbutton.set_active(False) - self.cqbutton.connect("toggled", self.enable_cq, "cq") - rightwintopbox.pack_start(self.cqbutton, False, False, 5) + self.cqbutton.connect("toggled", self.enable_cq) + rightwintopbox.pack_start(self.cqbutton, False, False, 3) self.cqbutton.show() self.beaconbutton = gtk.CheckButton("Beacon every 10 minutes") self.beaconbutton.set_active(True) - self.beaconbutton.connect("toggled", self.enable_beacon, "beacon") - rightwintopbox.pack_start(self.beaconbutton, False, False, 5) + self.beaconbutton.connect("toggled", self.enable_beacon) + rightwintopbox.pack_start(self.beaconbutton, False, False, 2) self.beaconbutton.show() rightwin.pack_start(rightwintopbox, False, False, 0) @@ -476,31 +485,38 @@ class APRSActivity(activity.Activity): messagebox = gtk.HBox(False, 4) - self.messagecombo = gtk.combo_box_entry_new_text() - self.messagecombo.append_text("ALL") - self.messagecombo.append_text("BEACON") - self.messagecombo.append_text("CQ") - self.messagecombo.append_text("QST") - self.messagecombo.append_text("CQSRVR") - self.messagecombo.set_active(-1) - self.messagedest = self.messagecombo.get_child() - self.messagedest.set_max_length(9) - self.messagedest.set_width_chars(5) - self.messagedest.modify_font(smallfont) - messagebox.pack_start(self.messagecombo, False, False, 0) - self.messagecombo.show() + self.messagetocall = gtk.Entry() + self.messagetocall.set_max_length(9) + self.messagetocall.set_width_chars(9) + self.messagetocall.modify_font(smallfont) + tocallcompletion = gtk.EntryCompletion() + self.tocalllist = gtk.ListStore(str) + self.tocalllist.append(["ALL"]) + self.tocalllist.append(["BEACON"]) + self.tocalllist.append(["CQ"]) + self.tocalllist.append(["QST"]) + self.tocalllist.append(["CQSRVR"]) + tocallcompletion.set_model(self.tocalllist) + self.messagetocall.set_completion(tocallcompletion) + tocallcompletion.set_text_column(0) + self.messagetocall.set_text("TO:") + self.messagetocall.select_region(0, -1) + tocallcompletion.connect("match-selected", self.tocall_selected) + messagebox.pack_start(self.messagetocall, False, False, 1) + self.messagetocall.show() self.messagetext = gtk.Entry() self.messagetext.set_max_length(67) - self.messagetext.set_width_chars(31) + self.messagetext.set_width_chars(30) self.messagetext.modify_font(smallfont) - self.messagetext.connect("activate", self.send_message, self.messagetext) +# self.messagetext.connect("activate", self.send_message, self.messagetext) + self.messagetext.connect("activate", self.send_message) messagebox.pack_start(self.messagetext, False, False, 0) self.messagetext.show() messagebutton = gtk.Button() messagebutton.set_label("Send") - messagebutton.connect("clicked", self.send_message, "message") + messagebutton.connect("clicked", self.send_message) messagebox.pack_start(messagebutton, False, False, 0) messagebutton.show() @@ -513,7 +529,7 @@ class APRSActivity(activity.Activity): self.statusview.set_justification(gtk.JUSTIFY_LEFT) self.statusview.modify_font(smallfont) - 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 \"\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") + 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 \"\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") self.statuswindow = gtk.ScrolledWindow() self.statuswindow.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_ALWAYS) @@ -560,14 +576,38 @@ class APRSActivity(activity.Activity): self.messagebuffer.set_text("") def clear_message_button(self, button=None): - # cancel all outgoing messages, for now + temp_queue_list = self.queue_list.copy() + temp_message_list = self.message_list.copy() + temp_current_message = self.current_message.copy() + temp_recv_acks = self.recv_acks.copy() + + # cancel all outgoing messages self.clear_msg_queue() - # clear bulletin list + + # clear seen bulletin list self.seen_bulletins = {} + # clear message screen self.clear_message() - # TODO re-add outgoing messages (reset text iters) - # TODO re-add queued messages + + # re-add outgoing messages (reset text iters) + if (temp_current_message != {}): + for call in temp_current_message: + sequence = temp_current_message[call] + id = "%s-%s" % (call, sequence) + message = self.current_message_text[call] + count = self.current_message_count[call] + delay = self.current_message_delay[call] + if (temp_recv_acks[id] != 1): + self.send_message(None, call, message, sequence, count, delay, False) + + # re-add queued messages + if (temp_queue_list != {}): + for call in temp_queue_list: + for sequence in temp_queue_list[call]: + id = "%s-%s" % (call, sequence) + message = temp_message_list[id] + self.send_message(None, call, message, sequence) def connect_aprs(self, button): if (self.sock == None): @@ -659,7 +699,7 @@ class APRSActivity(activity.Activity): self.stop_cq() # for test server only - if(HOST == "192.168.50.6"): + if(HOST == "192.168.50.14"): self.sock.sendall("q") self.sock.close() @@ -813,13 +853,14 @@ class APRSActivity(activity.Activity): self.validating = False return False try: - zipsock.connect(('geocoder.us', 80)) + zipsock.connect(('local.yahooapis.com', 80)) except socket.error, msg: self.status_write("[ERROR] %s\n" % msg[1]) self.validating = False return False try: - zipsock.sendall("GET /service/csv/geocode?zip=%s\n" % self.ziptext.get_text()) + APPID = "XtIUm.bV34FWFtK62fv24MszIckfwgYDjHJ1mTSmxoY2.iLe4zoPvPbM7Z8D" + zipsock.sendall("GET /MapsService/V1/geocode?appid=%s&zip=%s\n" % (APPID, self.ziptext.get_text())) except socket.error, msg: self.status_write("[ERROR] %s\n" % msg[1]) self.validating = False @@ -837,7 +878,9 @@ class APRSActivity(activity.Activity): self.calltext.set_text("X%s-%d%d" % (self.ziptext.get_text(), A, O)) self.passtext.set_text("-1") try: - (lat, lon, junk) = response.split(',', 2) + zipdata = parseString(response) + lat = (zipdata.getElementsByTagName('Latitude')[0]).childNodes[0].data.encode('iso-8859-1') + lon = (zipdata.getElementsByTagName('Longitude')[0]).childNodes[0].data.encode('iso-8859-1') except: self.status_write("%s\n" % response) lat = "0" @@ -968,7 +1011,7 @@ class APRSActivity(activity.Activity): JournalData['cq'] = 'False' callsignlist = [] - model = self.messagecombo.get_model() + model = self.tocalllist iter = model.get_iter_first() while iter: callsignlist.append(model.get(iter, 0)[0]) @@ -1194,15 +1237,15 @@ class APRSActivity(activity.Activity): pass - def disable_beacon(self, widget, data=None): + def disable_beacon(self, widget): if (self.sock != None): self.beaconbutton.set_active(False) - def enable_beacon(self, widget, data=None): + def enable_beacon(self, widget): if (not self.validating and widget.get_active()): self.validate_data() - def raw_send(self, widget, data=None): + def raw_send(self, widget): msg = "%s\n" % self.rawtext.get_text() try: self.sock.sendall(msg) @@ -1215,11 +1258,11 @@ class APRSActivity(activity.Activity): self.rawtext.set_text("") return True - def send_message(self, widget, data=None, tocall=None, message=None): + def send_message(self, widget=None, tocall=None, message=None, sequence=None, count=2, delay=7, start_timer=True): sendnow = False if (tocall == None and message == None): - tocall = self.messagedest.get_text().upper() + tocall = self.messagetocall.get_text().upper() message = self.messagetext.get_text() if (message == ""): @@ -1238,16 +1281,17 @@ class APRSActivity(activity.Activity): return False # add callsign to list if just entered - if (self.messagecombo.get_active() == -1): + if (self.last_selected != tocall): self.add_callsign(tocall, True) # get a sequence number - if (isbulletin): - # don't waste a number on a bulletin - sequence = "" - else: + if (sequence == None): sequence = "%s" % self.b90() + # in case clear window is called: + id = "%s-%s" % (tocall, sequence) + self.recv_acks[id] = 0 + # TODO # cancel message option - menu popup @@ -1265,7 +1309,7 @@ class APRSActivity(activity.Activity): self.messagetext.set_text("") if (sendnow): - self.send_msg_queue(tocall) + self.send_msg_queue(tocall, count, delay, start_timer) def b90(self): if (self.sequence > 8099): @@ -1281,8 +1325,13 @@ class APRSActivity(activity.Activity): if (self.recv_acks[id] == 1): return False else: - replyack = self.replyack(tocall) - self.send_data(":%s:%s{%s}%s" % (tocall.ljust(9), message, sequence, replyack)) + + isbulletin = self.bulletin_check(tocall) + if (isbulletin): + self.send_data(":%s:%s" % (tocall.ljust(9), message)) + else: + replyack = self.replyack(tocall) + self.send_data(":%s:%s{%s}%s" % (tocall.ljust(9), message, sequence, replyack)) count_start = self.messagebuffer.get_iter_at_mark(self.message_marks[id]) count_end = self.messagebuffer.get_iter_at_mark(self.message_marks[id]) @@ -1319,11 +1368,15 @@ class APRSActivity(activity.Activity): delay = 600 gobject.timeout_add(delay * 1000, self.msg_timer, tocall, message, sequence, count, delay) + # save count and delay for the clear button + self.current_message_count[tocall] = count + self.current_message_delay[tocall] = delay + # and stop this timer return False def add_callsign(self, callsign, activate): - model = self.messagecombo.get_model() + model = self.tocalllist notfound = True iter = model.get_iter_first() while iter: @@ -1333,9 +1386,9 @@ class APRSActivity(activity.Activity): break iter = model.iter_next(iter) if (notfound): - self.messagecombo.prepend_text(callsign) + self.tocalllist.append([callsign]) if (activate): - self.messagecombo.set_active(0) + self.messagetocall.set_text(callsign) def bulletin_check(self, callsign): # hard code CQSRVR @@ -1387,7 +1440,7 @@ class APRSActivity(activity.Activity): return True - def send_msg_queue(self, call): + def send_msg_queue(self, call, count=2, delay=7, start_timer=True): if (call in self.queue_list): if (self.queue_list[call] == []): del self.queue_list[call] @@ -1401,16 +1454,24 @@ class APRSActivity(activity.Activity): # record this so cancel can work on current messages self.current_message[call] = sequence + # record this so clear can re-add current messages + self.current_message_text[call] = message + + # save count and delay for the clear button + self.current_message_count[call] = count + self.current_message_delay[call] = delay + # send message isbulletin = self.bulletin_check(call) - if (isbulletin): - self.send_data(":%s:%s" % (call.ljust(9), message)) - gobject.timeout_add(600 * 1000, self.msg_timer, call, message, "", 2, 600) - else: - replyack = self.replyack(call) - self.send_data(":%s:%s{%s}%s" % (call.ljust(9), message, sequence, replyack)) - self.recv_acks["%s-%s" % (call, sequence)] = 0 - gobject.timeout_add(7 * 1000, self.msg_timer, call, message, sequence, 2, 7) + if (start_timer): + if (isbulletin): + self.send_data(":%s:%s" % (call.ljust(9), message)) + gobject.timeout_add(600 * 1000, self.msg_timer, call, message, sequence, count, 600) + else: + replyack = self.replyack(call) + self.send_data(":%s:%s{%s}%s" % (call.ljust(9), message, sequence, replyack)) + self.recv_acks["%s-%s" % (call, sequence)] = 0 + gobject.timeout_add(delay * 1000, self.msg_timer, call, message, sequence, count, delay) # reset the timestamp line_start = self.messagebuffer.get_iter_at_mark(self.message_marks[id]) @@ -1441,10 +1502,10 @@ class APRSActivity(activity.Activity): # add the counter iter = self.messagebuffer.get_iter_at_mark(self.message_marks[id]) - self.messagebuffer.insert(iter, " 1/%s" % MAXRETRIES) + self.messagebuffer.insert(iter, " %i/%s" % (count - 1, MAXRETRIES)) def cancel_message(self, id): - if (self.recv_acks[id] == 1): + if (id in self.recv_acks and self.recv_acks[id] == 1): return self.recv_acks[id] = 1 @@ -1525,7 +1586,7 @@ class APRSActivity(activity.Activity): # convert to string and mask off the high bit so number is always positive return str(hash & 0x7fff) - def hide_password(self, widget, data=None): + def hide_password(self, widget): if (widget.get_active()): self.passtext.set_visibility(False) else: @@ -1571,22 +1632,117 @@ class APRSActivity(activity.Activity): self.cq_watch = [] if (self.cqbutton.get_active()): # send_message fails because disconnect happens too fast. -# self.send_message(None, None, "CQSRVR", "U CQ") +# self.send_message(None, "CQSRVR", "U CQ") message = ":CQSRVR :U CQ{%s" % self.b90() self.send_data(message) - def enable_cq(self, widget, data=None): + def enable_cq(self, widget): if (self.sock != None): if (self.cqbutton.get_active()): self.send_cq() self.cq_watch.append(gobject.timeout_add(32 * 60 * 1000, self.send_cq)) else: self.stop_cq() - self.send_message(None, None, "CQSRVR", "U CQ") + self.send_message(None, "CQSRVR", "U CQ") def send_cq(self): if (self.sock == None): return False if (self.cqbutton.get_active()): - self.send_message(None, None, "CQSRVR", "CQ CQ CQ From %s" % self.stationtext.get_text()) + self.send_message(None, "CQSRVR", "CQ CQ CQ From %s" % self.stationtext.get_text()) + + def cancel_dialog(self, widget): + # I want this to be a palette popup menu instead of a dialog + + canceldialog = gtk.Dialog("Cancel Messages", None, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, (gtk.STOCK_CLOSE, gtk.RESPONSE_CLOSE)) + + if (self.queue_list == {} and self.current_message == {}): + label = gtk.Label(" No messages to cancel ") + canceldialog.vbox.pack_start(label, False, False, 0) + label.show() + + separator = gtk.HSeparator() + canceldialog.vbox.pack_start(separator, False, False, 6) + separator.show() + + else: + + label = gtk.Label(" Click button to cancel message ") + canceldialog.vbox.pack_start(label, False, False, 0) + label.show() + + separator = gtk.HSeparator() + canceldialog.vbox.pack_start(separator, False, False, 6) + separator.show() + + if (self.current_message != {}): + label = gtk.Label(" Sending ") + canceldialog.vbox.pack_start(label, False, False, 0) + label.show() + + for call in self.current_message: + sequence = self.current_message[call] + id = "%s-%s" % (call, sequence) + if (self.recv_acks[id] != 1): + button = gtk.Button() + button.set_label(str(call) + ", " + str(self.current_message_text[call])) + button.connect("clicked", self.cancel_cur_msg_button, call, sequence, id) + canceldialog.vbox.pack_start(button, False, False, 3) + button.show() + + separator = gtk.HSeparator() + canceldialog.vbox.pack_start(separator, False, False, 6) + separator.show() + + if (self.queue_list != {}): + label = gtk.Label(" In queue ") + canceldialog.vbox.pack_start(label, False, False, 0) + label.show() + + for call in self.queue_list: + for sequence in self.queue_list[call]: + id = "%s-%s" % (call, sequence) + button = gtk.Button() + button.set_label(str(call) + ", " + str(self.message_list[id])) + button.connect("clicked", self.cancel_queue_msg_button, call, sequence, id) + canceldialog.vbox.pack_start(button, False, False, 3) + button.show() + + separator = gtk.HSeparator() + canceldialog.vbox.pack_start(separator, False, False, 6) + separator.show() + + button = gtk.Button() + button.set_label("Cancel All") + button.connect("clicked", self.cancel_all_button) + canceldialog.vbox.pack_start(button, False, False, 3) + button.show() + + canceldialog.run() + canceldialog.destroy() + + def cancel_all_button(self, widget): + # button.vbox.dialog.destroy() + widget.parent.parent.destroy() + self.clear_msg_queue() + + def cancel_cur_msg_button(self, widget, call, sequence, id): + # button.vbox.dialog.destroy() + widget.parent.parent.destroy() + if (sequence == self.current_message[call]): + self.cancel_message(id) + del self.current_message[call] + self.send_msg_queue(call) + + def cancel_queue_msg_button(self, widget, call, sequence, id): + # button.vbox.dialog.destroy() + widget.parent.parent.destroy() + if (sequence in self.queue_list[call]): + self.cancel_message(id) + del self.message_list[id] + self.queue_list[call].remove(sequence) + if (self.queue_list[call] == []): + del self.queue_list[call] + def tocall_selected(self, completion, model, iter): + self.last_selected = model[iter][0] -- cgit v0.9.1