Making text on lists more thumb friendly
[watersofshiloah] / src / windows / _base.py
1 from __future__ import with_statement
2
3 import ConfigParser
4 import logging
5
6 import gobject
7 import gtk
8
9 import constants
10 import hildonize
11 import util.misc as misc_utils
12 import util.go_utils as go_utils
13
14 import stream_index
15 import banners
16 import playcontrol
17
18
19 _moduleLogger = logging.getLogger(__name__)
20
21
22 class BasicWindow(gobject.GObject, go_utils.AutoSignal):
23
24         __gsignals__ = {
25                 'quit' : (
26                         gobject.SIGNAL_RUN_LAST,
27                         gobject.TYPE_NONE,
28                         (),
29                 ),
30                 'home' : (
31                         gobject.SIGNAL_RUN_LAST,
32                         gobject.TYPE_NONE,
33                         (),
34                 ),
35                 'jump-to' : (
36                         gobject.SIGNAL_RUN_LAST,
37                         gobject.TYPE_NONE,
38                         (gobject.TYPE_PYOBJECT, ),
39                 ),
40                 'rotate' : (
41                         gobject.SIGNAL_RUN_LAST,
42                         gobject.TYPE_NONE,
43                         (gobject.TYPE_BOOLEAN, ),
44                 ),
45                 'fullscreen' : (
46                         gobject.SIGNAL_RUN_LAST,
47                         gobject.TYPE_NONE,
48                         (gobject.TYPE_BOOLEAN, ),
49                 ),
50         }
51
52         def __init__(self, app, player, store):
53                 gobject.GObject.__init__(self)
54                 self._isDestroyed = False
55
56                 self._app = app
57                 self._player = player
58                 self._store = store
59
60                 self._clipboard = gtk.clipboard_get()
61                 self._windowInFullscreen = False
62
63                 self._errorBanner = banners.StackingBanner()
64
65                 self._layout = gtk.VBox()
66                 self._layout.pack_start(self._errorBanner.toplevel, False, True)
67
68                 self._window = gtk.Window()
69                 go_utils.AutoSignal.__init__(self, self.window)
70                 self._window.add(self._layout)
71                 self._window = hildonize.hildonize_window(self._app, self._window)
72
73                 self._window.set_icon(self._store.get_pixbuf_from_store(self._store.STORE_LOOKUP["icon"]))
74                 self._window.connect("key-press-event", self._on_key_press)
75                 self._window.connect("window-state-event", self._on_window_state_change)
76                 self._window.connect("destroy", self._on_destroy)
77
78         @property
79         def window(self):
80                 return self._window
81
82         def show(self):
83                 hildonize.window_to_portrait(self._window)
84                 self._window.show_all()
85
86         def save_settings(self, config, sectionName):
87                 config.add_section(sectionName)
88                 config.set(sectionName, "fullscreen", str(self._windowInFullscreen))
89
90         def load_settings(self, config, sectionName):
91                 try:
92                         self._windowInFullscreen = config.getboolean(sectionName, "fullscreen")
93                 except ConfigParser.NoSectionError, e:
94                         _moduleLogger.info(
95                                 "Settings file %s is missing section %s" % (
96                                         constants._user_settings_,
97                                         e.section,
98                                 )
99                         )
100
101                 if self._windowInFullscreen:
102                         self._window.fullscreen()
103                 else:
104                         self._window.unfullscreen()
105
106         def jump_to(self, node):
107                 raise NotImplementedError("On %s" % self)
108
109         @misc_utils.log_exception(_moduleLogger)
110         def _on_destroy(self, *args):
111                 self._isDestroyed = True
112
113         @misc_utils.log_exception(_moduleLogger)
114         def _on_window_state_change(self, widget, event, *args):
115                 if event.new_window_state & gtk.gdk.WINDOW_STATE_FULLSCREEN:
116                         self._windowInFullscreen = True
117                 else:
118                         self._windowInFullscreen = False
119                 self.emit("fullscreen", self._windowInFullscreen)
120
121         @misc_utils.log_exception(_moduleLogger)
122         def _on_key_press(self, widget, event, *args):
123                 RETURN_TYPES = (gtk.keysyms.Return, gtk.keysyms.ISO_Enter, gtk.keysyms.KP_Enter)
124                 isCtrl = bool(event.get_state() & gtk.gdk.CONTROL_MASK)
125                 if (
126                         event.keyval == gtk.keysyms.F6 or
127                         event.keyval in RETURN_TYPES and isCtrl
128                 ):
129                         # The "Full screen" hardware key has been pressed
130                         if self._windowInFullscreen:
131                                 self._window.unfullscreen ()
132                         else:
133                                 self._window.fullscreen ()
134                         return True
135                 elif (
136                         event.keyval in (gtk.keysyms.w, ) and
137                         event.get_state() & gtk.gdk.CONTROL_MASK
138                 ):
139                         self._window.destroy()
140                 elif (
141                         event.keyval in (gtk.keysyms.q, ) and
142                         event.get_state() & gtk.gdk.CONTROL_MASK
143                 ):
144                         self.emit("quit")
145                 elif event.keyval == gtk.keysyms.l and event.get_state() & gtk.gdk.CONTROL_MASK:
146                         with open(constants._user_logpath_, "r") as f:
147                                 logLines = f.xreadlines()
148                                 log = "".join(logLines)
149                                 self._clipboard.set_text(str(log))
150                         return True
151
152         @misc_utils.log_exception(_moduleLogger)
153         def _on_home(self, *args):
154                 self.emit("home")
155                 self._window.destroy()
156
157         @misc_utils.log_exception(_moduleLogger)
158         def _on_jump(self, source, node):
159                 raise NotImplementedError("On %s" % self)
160
161         @misc_utils.log_exception(_moduleLogger)
162         def _on_quit(self, *args):
163                 self.emit("quit")
164                 self._window.destroy()
165
166
167 class ListWindow(BasicWindow):
168
169         def __init__(self, app, player, store, node):
170                 BasicWindow.__init__(self, app, player, store)
171                 self._node = node
172
173                 self.connect_auto(self._player, "title-change", self._on_player_title_change)
174
175                 self._loadingBanner = banners.GenericBanner()
176
177                 modelTypes, columns = zip(*self._get_columns())
178
179                 self._model = gtk.ListStore(*modelTypes)
180
181                 self._treeView = gtk.TreeView()
182                 self._treeView.connect("row-activated", self._on_row_activated)
183                 self._treeView.set_property("fixed-height-mode", True)
184                 self._treeView.set_headers_visible(False)
185                 self._treeView.set_model(self._model)
186                 for column in columns:
187                         if column is not None:
188                                 self._treeView.append_column(column)
189
190                 self._treeScroller = gtk.ScrolledWindow()
191                 self._treeScroller.add(self._treeView)
192                 self._treeScroller.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
193
194                 self._separator = gtk.HSeparator()
195                 self._playcontrol = playcontrol.NavControl(self._player, self._store)
196                 self._playcontrol.connect("home", self._on_home)
197                 self._playcontrol.connect("jump-to", self._on_jump)
198
199                 self._contentLayout = gtk.VBox(False)
200                 self._contentLayout.pack_start(self._treeScroller, True, True)
201                 self._contentLayout.pack_start(self._separator, False, True)
202                 self._contentLayout.pack_start(self._playcontrol.toplevel, False, True)
203
204                 self._layout.pack_start(self._loadingBanner.toplevel, False, False)
205                 self._layout.pack_start(self._contentLayout, True, True)
206
207         def show(self):
208                 BasicWindow.show(self)
209
210                 self._errorBanner.toplevel.hide()
211                 self._loadingBanner.toplevel.hide()
212
213                 self._refresh()
214                 self._playcontrol.refresh()
215
216         @classmethod
217         def _get_columns(cls):
218                 raise NotImplementedError("")
219
220         def _get_current_row(self):
221                 if self._player.node is None:
222                         return -1
223                 ancestors, current, descendants = stream_index.common_paths(self._player.node, self._node)
224                 if not descendants:
225                         return -1
226                 activeChild = descendants[0]
227                 for i, row in enumerate(self._model):
228                         if activeChild is row[0]:
229                                 return i
230                 else:
231                         return -1
232
233         def jump_to(self, node):
234                 ancestors, current, descendants = stream_index.common_paths(node, self._node)
235                 if current is None:
236                         raise RuntimeError("Cannot jump to node %s" % node)
237                 if not descendants:
238                         _moduleLogger.info("Current node is the target")
239                         return
240                 child = descendants[0]
241                 window = self._window_from_node(child)
242                 window.jump_to(node)
243
244         def _window_from_node(self, node):
245                 raise NotImplementedError("")
246
247         @misc_utils.log_exception(_moduleLogger)
248         def _on_row_activated(self, view, path, column):
249                 raise NotImplementedError("")
250
251         @misc_utils.log_exception(_moduleLogger)
252         def _on_player_title_change(self, player, node):
253                 self._select_row()
254
255         @misc_utils.log_exception(_moduleLogger)
256         def _on_jump(self, source, node):
257                 ancestors, current, descendants = stream_index.common_paths(node, self._node)
258                 if current is None:
259                         _moduleLogger.info("%s is not the target, moving up" % self._node)
260                         self.emit("jump-to", node)
261                         self._window.destroy()
262                         return
263                 if not descendants:
264                         _moduleLogger.info("Current node is the target")
265                         return
266                 child = descendants[0]
267                 window = self._window_from_node(child)
268                 window.jump_to(node)
269
270         def _show_loading(self):
271                 animationPath = self._store.STORE_LOOKUP["loading"]
272                 animation = self._store.get_pixbuf_animation_from_store(animationPath)
273                 self._loadingBanner.show(animation, "Loading...")
274
275         def _hide_loading(self):
276                 self._loadingBanner.hide()
277
278         def _refresh(self):
279                 self._show_loading()
280                 self._model.clear()
281
282         def _select_row(self):
283                 rowIndex = self._get_current_row()
284                 if rowIndex < 0:
285                         return
286                 path = (rowIndex, )
287                 self._treeView.scroll_to_cell(path)
288                 self._treeView.get_selection().select_path(path)