initial commit
[masstransit] / portrait.py
1 # -*- coding: utf-8 -*-
2 #
3 # gPodder - A media aggregator and podcast client
4 # Copyright (c) 2005-2010 Thomas Perl and the gPodder Team
5 #
6 # gPodder is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 3 of the License, or
9 # (at your option) any later version.
10 #
11 # gPodder is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 # GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
18 #
19
20 import dbus
21 import dbus.glib
22
23 import hildon
24 import osso
25
26 # Replace this with your own gettext() functionality
27 import gpodder
28 _ = gpodder.gettext
29
30
31 class FremantleRotation(object):
32     """thp's screen rotation for Maemo 5
33
34     Simply instantiate an object of this class and let it auto-rotate
35     your StackableWindows depending on the device orientation.
36
37     If you need to relayout a window, connect to its "configure-event"
38     signal and measure the ratio of width/height and relayout for that.
39
40     You can set the mode for rotation to AUTOMATIC (default), NEVER or
41     ALWAYS with the set_mode() method.
42     """
43     AUTOMATIC, NEVER, ALWAYS = range(3)
44
45     # Human-readable captions for the above constants
46     MODE_CAPTIONS = (_('Automatic'), _('Landscape'), _('Portrait'))
47
48     # Privately-used constants
49     _PORTRAIT, _LANDSCAPE = ('portrait', 'landscape')
50     _ENABLE_ACCEL = 'req_accelerometer_enable'
51     _DISABLE_ACCEL = 'req_accelerometer_disable'
52
53     # Defined in mce/dbus-names.h
54     _MCE_SERVICE = 'com.nokia.mce'
55     _MCE_REQUEST_PATH = '/com/nokia/mce/request'
56     _MCE_REQUEST_IF = 'com.nokia.mce.request'
57
58     def __init__(self, app_name, main_window=None, version='1.0', mode=0):
59         """Create a new rotation manager
60
61         app_name    ... The name of your application (for osso.Context)
62         main_window ... The root window (optional, hildon.StackableWindow)
63         version     ... The version of your application (optional, string)
64         mode        ... Initial mode for this manager (default: AUTOMATIC)
65         """
66         self._orientation = None
67         self._main_window = main_window
68         self._stack = hildon.WindowStack.get_default()
69         self._mode = -1
70         self._last_dbus_orientation = None
71         app_id = '-'.join((app_name, self.__class__.__name__))
72         self._osso_context = osso.Context(app_id, version, False)
73         program = hildon.Program.get_instance()
74         program.connect('notify::is-topmost', self._on_topmost_changed)
75         system_bus = dbus.Bus.get_system()
76         system_bus.add_signal_receiver(self._on_orientation_signal, \
77                 signal_name='sig_device_orientation_ind', \
78                 dbus_interface='com.nokia.mce.signal', \
79                 path='/com/nokia/mce/signal')
80         self.set_mode(mode)
81
82     def get_mode(self):
83         """Get the currently-set rotation mode
84
85         This will return one of three values: AUTOMATIC, ALWAYS or NEVER.
86         """
87         return self._mode
88
89     def set_mode(self, new_mode):
90         """Set the rotation mode
91
92         You can set the rotation mode to AUTOMATIC (use hardware rotation
93         info), ALWAYS (force portrait) and NEVER (force landscape).
94         """
95         if new_mode not in (self.AUTOMATIC, self.ALWAYS, self.NEVER):
96             raise ValueError('Unknown rotation mode')
97
98         if self._mode != new_mode:
99             if self._mode == self.AUTOMATIC:
100                 # Remember the current "automatic" orientation for later
101                 self._last_dbus_orientation = self._orientation
102                 # Tell MCE that we don't need the accelerometer anymore
103                 self._send_mce_request(self._DISABLE_ACCEL)
104
105             if new_mode == self.NEVER:
106                 self._orientation_changed(self._LANDSCAPE)
107             elif new_mode == self.ALWAYS:
108                 self._orientation_changed(self._PORTRAIT)
109             elif new_mode == self.AUTOMATIC:
110                 # Restore the last-known "automatic" orientation
111                 self._orientation_changed(self._last_dbus_orientation)
112                 # Tell MCE that we need the accelerometer again
113                 self._send_mce_request(self._ENABLE_ACCEL)
114
115             self._mode = new_mode
116
117     def _send_mce_request(self, request):
118         rpc = osso.Rpc(self._osso_context)
119         rpc.rpc_run(self._MCE_SERVICE, \
120                     self._MCE_REQUEST_PATH, \
121                     self._MCE_REQUEST_IF, \
122                     request, \
123                     use_system_bus=True)
124
125     def _on_topmost_changed(self, program, property_spec):
126         # XXX: This seems to never get called on Fremantle(?)
127         if self._mode == self.AUTOMATIC:
128             if program.get_is_topmost():
129                 self._send_mce_request(self._ENABLE_ACCEL)
130             else:
131                 self._send_mce_request(self._DISABLE_ACCEL)
132
133     def _get_main_window(self):
134         if self._main_window:
135             # If we have gotten the main window as parameter, return it and
136             # don't try "harder" to find another window using the stack
137             return self._main_window
138         else:
139             # The main window is at the "bottom" of the window stack, and as
140             # the list we get with get_windows() is sorted "topmost first", we
141             # simply take the last item of the list to get our main window
142             windows = self._stack.get_windows()
143             if windows:
144                 return windows[-1]
145             else:
146                 return None
147
148     def _orientation_changed(self, orientation):
149         if self._orientation == orientation:
150             # Ignore repeated requests
151             return
152
153         flags = hildon.PORTRAIT_MODE_SUPPORT
154         if orientation == self._PORTRAIT:
155             flags |= hildon.PORTRAIT_MODE_REQUEST
156
157         window = self._get_main_window()
158         if window is not None:
159             hildon.hildon_gtk_window_set_portrait_flags(window, flags)
160
161         self._orientation = orientation
162
163     def _on_orientation_signal(self, orientation, stand, face, x, y, z):
164         if orientation in (self._PORTRAIT, self._LANDSCAPE):
165             if self._mode == self.AUTOMATIC:
166                 # Automatically set the rotation based on hardware orientation
167                 self._orientation_changed(orientation)
168             else:
169                 # Ignore orientation changes for non-automatic modes, but save
170                 # the current orientation for "automatic" mode later on
171                 self._last_dbus_orientation = orientation
172