GTK+ GUI for configuring PD Buddy Sink devices
Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

pd-buddy-gtk.py 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347
  1. #!/usr/bin/env python3
  2. import sys
  3. import pdbuddy
  4. import gi
  5. gi.require_version('Gtk', '3.0')
  6. from gi.repository import Gtk, Gio, GObject, GLib
  7. def comms_error_dialog(parent, e):
  8. dialog = Gtk.MessageDialog(window, 0, Gtk.MessageType.ERROR,
  9. Gtk.ButtonsType.CLOSE, "Error communicating with device")
  10. dialog.format_secondary_text(e.strerror)
  11. dialog.run()
  12. dialog.destroy()
  13. class ListRowModel(GObject.GObject):
  14. def __init__(self, serport):
  15. GObject.GObject.__init__(self)
  16. self.serport = serport
  17. class SelectListStore(Gio.ListStore):
  18. def update_items(self):
  19. # Get a list of serial ports
  20. serports = list(pdbuddy.Sink.get_devices())
  21. # Mark ports to remove or add
  22. remove_list = []
  23. list_len = self.get_n_items()
  24. for i in range(list_len):
  25. remove = True
  26. for j in range(len(serports)):
  27. if serports[j] is not None and self.get_item(i).serport == serports[j]:
  28. serports[j] = None
  29. remove = False
  30. if remove:
  31. remove_list.append(i)
  32. # Remove the missing ones
  33. for i in remove_list:
  34. self.remove(i)
  35. # Add any new ports
  36. for port in serports:
  37. if port is not None:
  38. self.append(ListRowModel(port))
  39. class SelectList(Gtk.Box):
  40. __gsignals__ = {
  41. 'row-activated': (GObject.SIGNAL_RUN_FIRST, None,
  42. (object,))
  43. }
  44. def __init__(self):
  45. Gtk.Box.__init__(self)
  46. self._model = None
  47. self._builder = Gtk.Builder()
  48. self._builder.add_from_file("data/select-stack.ui")
  49. self._builder.connect_signals(self)
  50. sl = self._builder.get_object("select-list")
  51. # Add separators to the list
  52. sl.set_header_func(self._update_header_func, None)
  53. self.pack_start(self._builder.get_object("select-stack"), True, True, 0)
  54. self.show_all()
  55. def _update_header_func(self, row, before, data):
  56. """Add a separator header to all rows but the first one"""
  57. if before is None:
  58. row.set_header(None)
  59. return
  60. current = row.get_header()
  61. if current is None:
  62. current = Gtk.Separator.new(Gtk.Orientation.HORIZONTAL)
  63. row.set_header(current)
  64. def bind_model(self, model, func):
  65. self._builder.get_object("select-list").bind_model(model, func)
  66. self._model = model
  67. self.reload()
  68. GLib.timeout_add(1000, self.reload)
  69. def reload(self):
  70. self._model.update_items()
  71. # Set the visible child
  72. stack = self._builder.get_object("select-stack")
  73. if self._model.get_n_items():
  74. stack.set_visible_child(self._builder.get_object("select-frame"))
  75. else:
  76. stack.set_visible_child(self._builder.get_object("select-none"))
  77. return True
  78. def on_select_list_row_activated(self, box, row):
  79. self.emit("row-activated", row.model.serport)
  80. class SelectListRow(Gtk.ListBoxRow):
  81. def __init__(self, model):
  82. Gtk.EventBox.__init__(self)
  83. self.model = model
  84. self._builder = Gtk.Builder()
  85. self._builder.add_from_file("data/select-list-row.ui")
  86. self._builder.connect_signals(self)
  87. name = self._builder.get_object("name")
  88. name.set_text('{} {}'.format(self.model.serport.manufacturer,
  89. self.model.serport.product))
  90. device = self._builder.get_object("device")
  91. device.set_text(self.model.serport.device)
  92. self.add(self._builder.get_object("grid"))
  93. self.show_all()
  94. def on_identify_clicked(self, button):
  95. window = self.get_toplevel()
  96. try:
  97. with pdbuddy.Sink(self.model.serport) as pdbs:
  98. pdbs.identify()
  99. except OSError as e:
  100. comms_error_dialog(window, e)
  101. return
  102. class Handler:
  103. def __init__(self, builder):
  104. self.builder = builder
  105. self.serial_port = None
  106. self.voltage = None
  107. self.current = None
  108. self.giveback = None
  109. self.selectlist = None
  110. def on_pdb_window_realize(self, *args):
  111. # Get the list
  112. sb = self.builder.get_object("select-box")
  113. self.selectlist = SelectList()
  114. sb.pack_start(self.selectlist, True, True, 0)
  115. liststore = SelectListStore()
  116. self.selectlist.bind_model(liststore, SelectListRow)
  117. self.selectlist.connect("row-activated", self.on_select_list_row_activated)
  118. def on_pdb_window_delete_event(self, *args):
  119. Gtk.main_quit(*args)
  120. def on_select_list_row_activated(self, selectlist, serport):
  121. # Get voltage and current widgets
  122. voltage = self.builder.get_object("voltage-combobox")
  123. current = self.builder.get_object("current-spinbutton")
  124. giveback = self.builder.get_object("giveback-toggle")
  125. self.serial_port = serport
  126. window = self.builder.get_object("pdb-window")
  127. try:
  128. with pdbuddy.Sink(self.serial_port) as pdbs:
  129. pdbs.load()
  130. tmpcfg = pdbs.get_tmpcfg()
  131. except OSError as e:
  132. comms_error_dialog(window, e)
  133. return
  134. # Set giveback button state
  135. giveback.set_active(bool(tmpcfg.flags & pdbuddy.SinkFlags.GIVEBACK))
  136. # Get voltage and current from device and load them into the GUI
  137. if tmpcfg.v == 5000:
  138. voltage.set_active_id('voltage-five')
  139. elif tmpcfg.v == 9000:
  140. voltage.set_active_id('voltage-nine')
  141. elif tmpcfg.v == 15000:
  142. voltage.set_active_id('voltage-fifteen')
  143. if tmpcfg.v == 20000:
  144. voltage.set_active_id('voltage-twenty')
  145. current.set_value(tmpcfg.i/1000)
  146. self._store_device_settings()
  147. self._set_save_button_visibility()
  148. # Show the Sink page
  149. hst = self.builder.get_object("header-stack")
  150. hsink = self.builder.get_object("header-sink")
  151. hsink.set_title('{} {}'.format(serport.manufacturer, serport.product))
  152. hsink.set_subtitle(serport.device)
  153. hst.set_visible_child(hsink)
  154. st = self.builder.get_object("stack")
  155. sink = self.builder.get_object("sink")
  156. st.set_visible_child(sink)
  157. # Ping the Sink repeatedly
  158. GLib.timeout_add(1000, self._ping)
  159. def _ping(self):
  160. """Ping the device we're configuring, showing to the list on failure"""
  161. if self.serial_port is None:
  162. self.selectlist.reload()
  163. self.on_header_sink_back_clicked(None)
  164. return False
  165. try:
  166. with pdbuddy.Sink(self.serial_port) as pdbs:
  167. pdbs.send_command("")
  168. return True
  169. except:
  170. self.selectlist.reload()
  171. self.on_header_sink_back_clicked(None)
  172. return False
  173. def on_header_sink_back_clicked(self, data):
  174. self.serial_port = None
  175. # Show the Select page
  176. hst = self.builder.get_object("header-stack")
  177. hselect = self.builder.get_object("header-select")
  178. hst.set_visible_child(hselect)
  179. st = self.builder.get_object("stack")
  180. select = self.builder.get_object("select")
  181. st.set_visible_child(select)
  182. def on_header_sink_save_clicked(self, button):
  183. window = self.builder.get_object("pdb-window")
  184. try:
  185. with pdbuddy.Sink(self.serial_port) as pdbs:
  186. pdbs.write()
  187. self._store_device_settings()
  188. self._set_save_button_visibility()
  189. except OSError as e:
  190. comms_error_dialog(window, e)
  191. self.on_header_sink_back_clicked(None)
  192. def _store_device_settings(self):
  193. """Store the settings that were loaded from the device"""
  194. # Get voltage and current widgets
  195. voltage = self.builder.get_object("voltage-combobox")
  196. current = self.builder.get_object("current-spinbutton")
  197. giveback = self.builder.get_object("giveback-toggle")
  198. # Remember the loaded settings
  199. self.voltage = voltage.get_active_id()
  200. self.current = current.get_value()
  201. self.giveback = giveback.get_active()
  202. def _set_save_button_visibility(self):
  203. """Show the save button if there are new settings to save"""
  204. # Get relevant widgets
  205. voltage = self.builder.get_object("voltage-combobox")
  206. current = self.builder.get_object("current-spinbutton")
  207. giveback = self.builder.get_object("giveback-toggle")
  208. rev = self.builder.get_object("header-sink-save-revealer")
  209. # Set visibility
  210. rev.set_reveal_child(voltage.get_active_id() != self.voltage
  211. or current.get_value() != self.current
  212. or giveback.get_active() != self.giveback)
  213. def on_voltage_combobox_changed(self, combo):
  214. window = self.builder.get_object("pdb-window")
  215. try:
  216. with pdbuddy.Sink(self.serial_port) as pdbs:
  217. pdbs.set_v(int(combo.get_active_text())*1000)
  218. self._set_save_button_visibility()
  219. except OSError as e:
  220. comms_error_dialog(window, e)
  221. self.on_header_sink_back_clicked(None)
  222. def on_current_spinbutton_changed(self, spin):
  223. window = self.builder.get_object("pdb-window")
  224. try:
  225. with pdbuddy.Sink(self.serial_port) as pdbs:
  226. pdbs.set_i(int(spin.get_value())*1000)
  227. self._set_save_button_visibility()
  228. except OSError as e:
  229. comms_error_dialog(window, e)
  230. self.on_header_sink_back_clicked(None)
  231. def on_giveback_toggle_toggled(self, toggle):
  232. window = self.builder.get_object("pdb-window")
  233. try:
  234. with pdbuddy.Sink(self.serial_port) as pdbs:
  235. pdbs.toggle_giveback()
  236. self._set_save_button_visibility()
  237. except OSError as e:
  238. comms_error_dialog(window, e)
  239. self.on_header_sink_back_clicked(None)
  240. class Application(Gtk.Application):
  241. def __init__(self, *args, **kwargs):
  242. super().__init__(*args, application_id="com.clayhobbs.pd-buddy-gtk",
  243. **kwargs)
  244. self.window = None
  245. def do_startup(self):
  246. Gtk.Application.do_startup(self)
  247. self.builder = Gtk.Builder()
  248. self.builder.add_from_file("data/pd-buddy-gtk.ui")
  249. self.builder.connect_signals(Handler(self.builder))
  250. def do_activate(self):
  251. # We only allow a single window and raise any existing ones
  252. if not self.window:
  253. # Windows are associated with the application
  254. # when the last one is closed the application shuts down
  255. self.window = self.builder.get_object("pdb-window")
  256. self.add_window(self.window)
  257. self.window.set_wmclass("PD Buddy Configuration",
  258. "PD Buddy Configuration")
  259. self.window.present()
  260. def run():
  261. app = Application()
  262. app.run(sys.argv)
  263. if __name__ == "__main__":
  264. run()