|
1 | 1 | # -*- coding: utf-8 -*-
|
2 |
| -# http://www.radio-browser.info/webservice#Advanced_station_search |
3 | 2 | import curses
|
4 | 3 | try:
|
5 | 4 | from dns import resolver
|
|
19 | 18 | from .player import info_dict_to_list
|
20 | 19 | from .cjkwrap import cjklen, PY3
|
21 | 20 | from .countries import countries
|
| 21 | +from .simple_curses_widgets import SimpleCursesLineEdit, SimpleCursesHorizontalPushButtons |
22 | 22 |
|
23 | 23 | import locale
|
24 | 24 | locale.setlocale(locale.LC_ALL, '') # set your locale
|
@@ -50,14 +50,14 @@ class PyRadioStationsBrowser(object):
|
50 | 50 |
|
51 | 51 | BASE_URL = ''
|
52 | 52 | TITLE = ''
|
53 |
| - _parent = None |
| 53 | + _parent = _outer_parent = None |
54 | 54 | _raw_stations = []
|
55 | 55 | _last_search = None
|
56 | 56 | _internal_header_height = 0
|
57 | 57 | _url_timeout = 3
|
58 | 58 | _search_timeout = 3
|
59 | 59 | _vote_callback = None
|
60 |
| - _sort = None |
| 60 | + _sort = _sort_win = None |
61 | 61 |
|
62 | 62 | # Normally outer boddy (holding box, header, internal header) is
|
63 | 63 | # 2 chars wider that the internal body (holding the stations)
|
@@ -99,6 +99,16 @@ def parent(self, val):
|
99 | 99 | if self._sort:
|
100 | 100 | self._sort._parent = val
|
101 | 101 |
|
| 102 | + @property |
| 103 | + def outer_parent(self): |
| 104 | + return self._outer_parent |
| 105 | + |
| 106 | + @outer_parent.setter |
| 107 | + def outer_parent(self, val): |
| 108 | + self._outer_parent = val |
| 109 | + if self._sort_win: |
| 110 | + self._sort_win._parent = val |
| 111 | + |
102 | 112 | @property
|
103 | 113 | def outer_internal_body_half_diff(self):
|
104 | 114 | return self._outer_internal_body_half_diff
|
@@ -322,14 +332,16 @@ def url(self, id_in_list):
|
322 | 332 | return ''
|
323 | 333 |
|
324 | 334 | def click(self, a_station):
|
325 |
| - url = 'http://' + self._server + '/json/url/' + self._raw_stations[a_station]['stationuuid'] |
326 |
| - try: |
327 |
| - r = self._session.get(url=url, headers=self._headers, timeout=(self._search_timeout, 2 * self._search_timeout)) |
328 |
| - if logger.isEnabledFor(logging.DEBUG): |
329 |
| - logger.debug('Station click result: "{}"'.format(r.text)) |
330 |
| - except: |
331 |
| - if logger.isEnabledFor(logging.DEBUG): |
332 |
| - logger.debug('Station click failed...') |
| 335 | + def do_click(a_station_uuid): |
| 336 | + url = 'http://' + self._server + '/json/url/' + a_station_uuid |
| 337 | + try: |
| 338 | + r = self._session.get(url=url, headers=self._headers, timeout=(self._search_timeout, 2 * self._search_timeout)) |
| 339 | + if logger.isEnabledFor(logging.DEBUG): |
| 340 | + logger.debug('Station click result: "{}"'.format(r.text)) |
| 341 | + except: |
| 342 | + if logger.isEnabledFor(logging.DEBUG): |
| 343 | + logger.debug('Station click failed...') |
| 344 | + threading.Thread(target=do_click, args=(self._raw_stations[a_station]['stationuuid'], )).start() |
333 | 345 |
|
334 | 346 | def vote(self, a_station):
|
335 | 347 | url = 'http://' + self._server + '/json/vote/' + self._raw_stations[a_station]['stationuuid']
|
@@ -1082,6 +1094,192 @@ def keypress(self, char):
|
1082 | 1094 |
|
1083 | 1095 | return ret
|
1084 | 1096 |
|
| 1097 | + def do_search(self, parent=None, init=False): |
| 1098 | + if init: |
| 1099 | + self._sort_win = RadioBrowserInfoSearchWindow( |
| 1100 | + parent=parent, |
| 1101 | + init=init |
| 1102 | + ) |
| 1103 | + self.keyboard_handler = self._sort_win |
| 1104 | + self._sort_win.show() |
| 1105 | + |
| 1106 | +class RadioBrowserInfoSearchWindow(object): |
| 1107 | + |
| 1108 | + search_by_items = ( |
| 1109 | + 'Name', |
| 1110 | + 'Tag', |
| 1111 | + 'Country', |
| 1112 | + 'State', |
| 1113 | + 'Codec', |
| 1114 | + 'Language', |
| 1115 | + ) |
| 1116 | + |
| 1117 | + sort_by_items = ( |
| 1118 | + 'None', |
| 1119 | + 'Random', |
| 1120 | + 'Name', |
| 1121 | + 'Tag', |
| 1122 | + 'Country', |
| 1123 | + 'State', |
| 1124 | + 'Language', |
| 1125 | + 'Votes', |
| 1126 | + 'Clicks', |
| 1127 | + 'Bitrate', |
| 1128 | + 'Codec', |
| 1129 | + ) |
| 1130 | + |
| 1131 | + def __init__(self, |
| 1132 | + parent, |
| 1133 | + init=False |
| 1134 | + ): |
| 1135 | + self._parent = parent |
| 1136 | + self._init = init |
| 1137 | + self._too_small = False |
| 1138 | + self._focus = 0 |
| 1139 | + self._win = None |
| 1140 | + self.maxY = self.maxX = 0 |
| 1141 | + self.TITLE = ' Radio Browser Search ' |
| 1142 | + |
| 1143 | + self._widgets = [ None, None, None, None, None ] |
| 1144 | + |
| 1145 | + @property |
| 1146 | + def focus(self): |
| 1147 | + return self._focus |
| 1148 | + |
| 1149 | + @focus.setter |
| 1150 | + def focus(self, val): |
| 1151 | + if val in range(0, len(self._widgets)): |
| 1152 | + self._focus = val |
| 1153 | + else: |
| 1154 | + if val < 0: |
| 1155 | + self._focus = len(self._widgets) - 1 |
| 1156 | + else: |
| 1157 | + self._focus = 0 |
| 1158 | + self.show() |
| 1159 | + |
| 1160 | + def show(self): |
| 1161 | + pY, pX = self._parent.getmaxyx() |
| 1162 | + logger.error('DE pY = {}, pX = {}'.format(pY, pX)) |
| 1163 | + self.Y, self.X = self._parent.getbegyx() |
| 1164 | + |
| 1165 | + if self.maxY != pY or self.maxX != pX: |
| 1166 | + logger.error('DE --== SEARCH ==--') |
| 1167 | + pY, pX = self._parent.getmaxyx() |
| 1168 | + logger.error('DE pY = {}, pX = {}'.format(pY, pX)) |
| 1169 | + self.maxY = pY |
| 1170 | + self.maxX = pX |
| 1171 | + self._win = self._parent |
| 1172 | + # self._win = curses.newwin( |
| 1173 | + # self.maxY, self.maxX, |
| 1174 | + # Y, X |
| 1175 | + # ) |
| 1176 | + self._win.bkgdset(' ', curses.color_pair(5)) |
| 1177 | + # self._win.erase() |
| 1178 | + self._win.box() |
| 1179 | + self._win.addstr(0, int((self.maxX - len(self.TITLE)) / 2), |
| 1180 | + self.TITLE, |
| 1181 | + curses.color_pair(4)) |
| 1182 | + self._win.refresh() |
| 1183 | + self._erase_win(self.maxY, self.maxX, self.Y, self.X) |
| 1184 | + |
| 1185 | + ''' start displaying things ''' |
| 1186 | + self._win.addstr(1, 2, 'Search for', curses.color_pair(5)) |
| 1187 | + self._win.addstr(4, 2, 'Search by', curses.color_pair(5)) |
| 1188 | + |
| 1189 | + for i, n in enumerate(self._widgets): |
| 1190 | + if n is None: |
| 1191 | + if i == 0: |
| 1192 | + self._widgets[0] = SimpleCursesLineEdit( |
| 1193 | + parent=self._win, |
| 1194 | + width=-2, |
| 1195 | + begin_y=3, |
| 1196 | + begin_x=2, |
| 1197 | + boxed=False, |
| 1198 | + has_history=False, |
| 1199 | + caption='', |
| 1200 | + box_color=curses.color_pair(9), |
| 1201 | + caption_color=curses.color_pair(4), |
| 1202 | + edit_color=curses.color_pair(9), |
| 1203 | + cursor_color=curses.color_pair(8), |
| 1204 | + unfocused_color=curses.color_pair(5), |
| 1205 | + string_changed_handler='') |
| 1206 | + self._widgets[0].bracket = False |
| 1207 | + self._line_editor = self._widgets[0] |
| 1208 | + elif i == 1: |
| 1209 | + self._widgets[i] = None |
| 1210 | + elif i == 2: |
| 1211 | + self._widgets[i] = None |
| 1212 | + elif i == 3: |
| 1213 | + self._widgets[i] = None |
| 1214 | + elif i == 4: |
| 1215 | + self._widgets[i] = None |
| 1216 | + ''' add horizontal push buttons ''' |
| 1217 | + self._h_buttons = SimpleCursesHorizontalPushButtons( |
| 1218 | + Y=6, captions=('OK', 'Cancel'), |
| 1219 | + color_focused=curses.color_pair(9), |
| 1220 | + color=curses.color_pair(4), |
| 1221 | + bracket_color=curses.color_pair(5), |
| 1222 | + parent=self._win) |
| 1223 | + #self._h_buttons.calculate_buttons_position() |
| 1224 | + self._widgets[4], self._widgets[5] = self._h_buttons.buttons |
| 1225 | + self._widgets[4]._focused = self._widgets[5].focused = False |
| 1226 | + |
| 1227 | + self._win.refresh() |
| 1228 | + self._update_focus() |
| 1229 | + if not self._too_small: |
| 1230 | + self._line_editor.show(self._win, opening=False) |
| 1231 | + self._h_buttons.calculate_buttons_position() |
| 1232 | + self._widgets[1].show() |
| 1233 | + self._widgets[2].show() |
| 1234 | + # self._refresh() |
| 1235 | + |
| 1236 | + def _update_focus(self): |
| 1237 | + # use _focused here to avoid triggering |
| 1238 | + # widgets' refresh |
| 1239 | + for i, x in enumerate(self._widgets): |
| 1240 | + if x: |
| 1241 | + if self._focus == i: |
| 1242 | + x._focused = True |
| 1243 | + else: |
| 1244 | + x._focused = False |
| 1245 | + |
| 1246 | + def _erase_win(self, pY, pX, Y, X): |
| 1247 | + empty_win = curses.newwin( |
| 1248 | + pY - 2, pX - 2, |
| 1249 | + Y + 1, X + 1 |
| 1250 | + ) |
| 1251 | + empty_win.bkgdset(' ', curses.color_pair(5)) |
| 1252 | + empty_win.erase() |
| 1253 | + empty_win.refresh() |
| 1254 | + |
| 1255 | + def keypress(self, char): |
| 1256 | + ''' RadioBrowserInfoSearchWindow keypress |
| 1257 | +
|
| 1258 | + Returns |
| 1259 | + ------- |
| 1260 | + -1 - Cancel |
| 1261 | + 0 - do search |
| 1262 | + 1 - Continue |
| 1263 | + 2 - Display help |
| 1264 | + ''' |
| 1265 | + if self._too_small: |
| 1266 | + return 1 |
| 1267 | + |
| 1268 | + if char == ord('?'): |
| 1269 | + return 2 |
| 1270 | + |
| 1271 | + if char in ( |
| 1272 | + curses.KEY_EXIT, ord('q'), 27, |
| 1273 | + ord('h'), curses.KEY_LEFT |
| 1274 | + ): |
| 1275 | + return -1 |
| 1276 | + |
| 1277 | + elif char in ( |
| 1278 | + ord('l'), ord(' '), ord('\n'), ord('\r'), |
| 1279 | + curses.KEY_RIGHT, curses.KEY_ENTER |
| 1280 | + ): |
| 1281 | + return 0 |
| 1282 | + |
1085 | 1283 | class RadioBrowserInfoData(object):
|
1086 | 1284 | ''' Read search parameters for radio.browser.info service
|
1087 | 1285 |
|
|
0 commit comments