1
|
# Copyright 2008 Dan Smith <dsmith@danplanet.com>
|
2
|
#
|
3
|
# This program is free software: you can redistribute it and/or modify
|
4
|
# it under the terms of the GNU General Public License as published by
|
5
|
# the Free Software Foundation, either version 3 of the License, or
|
6
|
# (at your option) any later version.
|
7
|
#
|
8
|
# This program is distributed in the hope that it will be useful,
|
9
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
10
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
11
|
# GNU General Public License for more details.
|
12
|
#
|
13
|
# You should have received a copy of the GNU General Public License
|
14
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
15
|
|
16
|
import threading
|
17
|
import os
|
18
|
|
19
|
import gtk
|
20
|
import gobject
|
21
|
|
22
|
from chirp import platform, directory, detect, chirp_common
|
23
|
from chirpui import miscwidgets, cloneprog, inputdialog, common, config
|
24
|
|
25
|
AUTO_DETECT_STRING = "Auto Detect (Icom Only)"
|
26
|
|
27
|
class CloneSettings:
|
28
|
def __init__(self):
|
29
|
self.port = None
|
30
|
self.radio_class = None
|
31
|
|
32
|
def __str__(self):
|
33
|
s = ""
|
34
|
if self.radio_class:
|
35
|
return _("{vendor} {model} on {port}").format(\
|
36
|
vendor=self.radio_class.VENDOR,
|
37
|
model=self.radio_class.MODEL,
|
38
|
port=self.port)
|
39
|
|
40
|
class CloneSettingsDialog(gtk.Dialog):
|
41
|
def __make_field(self, label, widget):
|
42
|
l = gtk.Label(label)
|
43
|
self.__table.attach(l, 0, 1, self.__row, self.__row+1)
|
44
|
self.__table.attach(widget, 1, 2, self.__row, self.__row+1)
|
45
|
self.__row += 1
|
46
|
|
47
|
l.show()
|
48
|
widget.show()
|
49
|
|
50
|
def __make_port(self, port):
|
51
|
conf = config.get("state")
|
52
|
|
53
|
ports = platform.get_platform().list_serial_ports()
|
54
|
if not port:
|
55
|
if conf.get("last_port"):
|
56
|
port = conf.get("last_port")
|
57
|
elif ports:
|
58
|
port = ports[0]
|
59
|
else:
|
60
|
port = ""
|
61
|
if not port in ports:
|
62
|
ports.insert(0, port)
|
63
|
|
64
|
return miscwidgets.make_choice(ports, True, port)
|
65
|
|
66
|
def __make_model(self):
|
67
|
return miscwidgets.make_choice([], False)
|
68
|
|
69
|
def __make_vendor(self, model):
|
70
|
vendors = {}
|
71
|
for rclass in sorted(directory.DRV_TO_RADIO.values()):
|
72
|
if not issubclass(rclass, chirp_common.CloneModeRadio) and \
|
73
|
not issubclass(rclass, chirp_common.LiveRadio):
|
74
|
continue
|
75
|
|
76
|
if not vendors.has_key(rclass.VENDOR):
|
77
|
vendors[rclass.VENDOR] = []
|
78
|
|
79
|
vendors[rclass.VENDOR].append(rclass)
|
80
|
|
81
|
self.__vendors = vendors
|
82
|
|
83
|
conf = config.get("state")
|
84
|
if not conf.get("last_vendor"):
|
85
|
conf.set("last_vendor", sorted(vendors.keys())[0])
|
86
|
|
87
|
last_vendor = conf.get("last_vendor")
|
88
|
if last_vendor not in vendors.keys():
|
89
|
last_vendor = vendors.keys()[0]
|
90
|
|
91
|
v = miscwidgets.make_choice(sorted(vendors.keys()), False, last_vendor)
|
92
|
|
93
|
def _changed(box, vendors, model):
|
94
|
models = vendors[box.get_active_text()]
|
95
|
|
96
|
added_models = []
|
97
|
|
98
|
model.get_model().clear()
|
99
|
for rclass in sorted(models, key=lambda c: c.__name__):
|
100
|
if rclass.MODEL not in added_models:
|
101
|
model.append_text(rclass.MODEL)
|
102
|
added_models.append(rclass.MODEL)
|
103
|
|
104
|
if box.get_active_text() in detect.DETECT_FUNCTIONS:
|
105
|
model.insert_text(0, _("Detect"))
|
106
|
added_models.insert(0, _("Detect"))
|
107
|
|
108
|
model_names = [x.MODEL for x in models]
|
109
|
if conf.get("last_model") in model_names:
|
110
|
model.set_active(added_models.index(conf.get("last_model")))
|
111
|
else:
|
112
|
model.set_active(0)
|
113
|
|
114
|
v.connect("changed", _changed, vendors, model)
|
115
|
_changed(v, vendors, model)
|
116
|
|
117
|
return v
|
118
|
|
119
|
def __make_ui(self, settings):
|
120
|
self.__table = gtk.Table(3, 2)
|
121
|
self.__table.set_row_spacings(3)
|
122
|
self.__table.set_col_spacings(10)
|
123
|
self.__row = 0
|
124
|
|
125
|
self.__port = self.__make_port(settings and settings.port or None)
|
126
|
self.__modl = self.__make_model()
|
127
|
self.__vend = self.__make_vendor(self.__modl)
|
128
|
|
129
|
self.__make_field(_("Port"), self.__port)
|
130
|
self.__make_field(_("Vendor"), self.__vend)
|
131
|
self.__make_field(_("Model"), self.__modl)
|
132
|
|
133
|
if settings and settings.radio_class:
|
134
|
common.combo_select(self.__vend, settings.radio_class.VENDOR)
|
135
|
self.__modl.get_model().clear()
|
136
|
self.__modl.append_text(settings.radio_class.MODEL)
|
137
|
common.combo_select(self.__modl, settings.radio_class.MODEL)
|
138
|
self.__vend.set_sensitive(False)
|
139
|
self.__modl.set_sensitive(False)
|
140
|
self.__prompt_force_after = gtk.CheckButton()
|
141
|
try:
|
142
|
self.__prompt_force_after.set_tooltip_text(
|
143
|
_("Try this if download work but upload fails immediately"))
|
144
|
except:
|
145
|
pass
|
146
|
self.__prompt_force_after.set_active(
|
147
|
config.get("upload_prompt").get_bool("force_after"))
|
148
|
self.__make_field(
|
149
|
_("Force alternative\nopen schema"),
|
150
|
self.__prompt_force_after)
|
151
|
|
152
|
self.__table.show()
|
153
|
self.vbox.pack_start(self.__table, 1, 1, 1)
|
154
|
|
155
|
def __init__(self, settings=None, parent=None, title=_("Radio")):
|
156
|
buttons = (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
|
157
|
gtk.STOCK_OK, gtk.RESPONSE_OK)
|
158
|
gtk.Dialog.__init__(self, title,
|
159
|
parent=parent,
|
160
|
flags=gtk.DIALOG_MODAL)
|
161
|
self.__make_ui(settings)
|
162
|
self.__cancel_button = self.add_button(gtk.STOCK_CANCEL,
|
163
|
gtk.RESPONSE_CANCEL)
|
164
|
self.__okay_button = self.add_button(gtk.STOCK_OK,
|
165
|
gtk.RESPONSE_OK)
|
166
|
self.__okay_button.grab_default()
|
167
|
self.__okay_button.grab_focus()
|
168
|
|
169
|
def run(self):
|
170
|
r = gtk.Dialog.run(self)
|
171
|
if r != gtk.RESPONSE_OK:
|
172
|
return None
|
173
|
|
174
|
vendor = self.__vend.get_active_text()
|
175
|
model = self.__modl.get_active_text()
|
176
|
|
177
|
cs = CloneSettings()
|
178
|
cs.port = self.__port.get_active_text()
|
179
|
if model == _("Detect"):
|
180
|
try:
|
181
|
cs.radio_class = detect.DETECT_FUNCTIONS[vendor](cs.port)
|
182
|
if not cs.radio_class:
|
183
|
raise Exception(_("Unable to detect radio on {port}").format(port=cs.port))
|
184
|
except Exception, e:
|
185
|
d = inputdialog.ExceptionDialog(e)
|
186
|
d.run()
|
187
|
d.destroy()
|
188
|
return None
|
189
|
else:
|
190
|
for rclass in directory.DRV_TO_RADIO.values():
|
191
|
if rclass.MODEL == model:
|
192
|
cs.radio_class = rclass
|
193
|
break
|
194
|
if not cs.radio_class:
|
195
|
common.show_error(_("Internal error: Unable to upload to {model}").format(model=model))
|
196
|
print self.__vendors
|
197
|
return None
|
198
|
|
199
|
conf = config.get("state")
|
200
|
conf.set("last_port", cs.port)
|
201
|
conf.set("last_vendor", cs.radio_class.VENDOR)
|
202
|
conf.set("last_model", model)
|
203
|
try:
|
204
|
if self.__prompt_force_after != None:
|
205
|
config.get("upload_prompt").set_bool("force_after", self.__prompt_force_after.get_active())
|
206
|
except:
|
207
|
pass
|
208
|
|
209
|
return cs
|
210
|
|
211
|
class CloneCancelledException(Exception):
|
212
|
pass
|
213
|
|
214
|
class CloneThread(threading.Thread):
|
215
|
def __status(self, status):
|
216
|
gobject.idle_add(self.__progw.status, status)
|
217
|
|
218
|
def __init__(self, radio, direction, cb=None, parent=None):
|
219
|
threading.Thread.__init__(self)
|
220
|
|
221
|
self.__radio = radio
|
222
|
self.__out = direction == "out"
|
223
|
self.__cback = cb
|
224
|
self.__cancelled = False
|
225
|
|
226
|
self.__progw = cloneprog.CloneProg(parent=parent, cancel=self.cancel)
|
227
|
|
228
|
def cancel(self):
|
229
|
self.__radio.pipe.close()
|
230
|
self.__cancelled = True
|
231
|
|
232
|
def run(self):
|
233
|
print "Clone thread started"
|
234
|
|
235
|
gobject.idle_add(self.__progw.show)
|
236
|
|
237
|
self.__radio.status_fn = self.__status
|
238
|
|
239
|
try:
|
240
|
if self.__out:
|
241
|
self.__radio.sync_out()
|
242
|
else:
|
243
|
self.__radio.sync_in()
|
244
|
|
245
|
emsg = None
|
246
|
except Exception, e:
|
247
|
common.log_exception()
|
248
|
print _("Clone failed: {error}").format(error=e)
|
249
|
emsg = e
|
250
|
|
251
|
gobject.idle_add(self.__progw.hide)
|
252
|
|
253
|
# NB: Compulsory close of the radio's serial connection
|
254
|
self.__radio.pipe.close()
|
255
|
|
256
|
print "Clone thread ended"
|
257
|
|
258
|
if self.__cback and not self.__cancelled:
|
259
|
gobject.idle_add(self.__cback, self.__radio, emsg)
|
260
|
|
261
|
if __name__ == "__main__":
|
262
|
d = CloneSettingsDialog("/dev/ttyUSB0")
|
263
|
r = d.run()
|
264
|
print r
|