~facundo/magicicada-gui/listen-progress-signals

« back to all changes in this revision

Viewing changes to magicicada/gui/gtk/status.py

  • Committer: Natalia B. Bidart
  • Date: 2012-02-23 00:57:52 UTC
  • mfrom: (121.3.12 better-status-info)
  • Revision ID: natalia.bidart@canonical.com-20120223005752-oud3463ras5g1ars
Tags: release-0.4.2
- Redesigned status bar so the status info is shown in a clearer and simpler way
  (LP: #818217).

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# -*- coding: utf-8 -*-
 
2
#
 
3
# Copyright 2010-2012 Chicharreros
 
4
#
 
5
# This program is free software: you can redistribute it and/or modify it
 
6
# under the terms of the GNU General Public License version 3, as published
 
7
# by the Free Software Foundation.
 
8
#
 
9
# This program is distributed in the hope that it will be useful, but
 
10
# WITHOUT ANY WARRANTY; without even the implied warranties of
 
11
# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
 
12
# PURPOSE.  See the GNU General Public License for more details.
 
13
#
 
14
# You should have received a copy of the GNU General Public License along
 
15
# with this program.  If not, see <http://www.gnu.org/licenses/>.
 
16
 
 
17
"""Magicicada Status widget."""
 
18
 
 
19
import logging
 
20
import os
 
21
import time
 
22
 
 
23
from gettext import gettext as _
 
24
from twisted.internet import defer
 
25
 
 
26
import gtk
 
27
 
 
28
from magicicada import syncdaemon
 
29
from magicicada.dbusiface import NOT_SYNCHED_PATH
 
30
from magicicada.helpers import log, humanize_bytes
 
31
from magicicada.gui.gtk.helpers import Buildable, build_icon_dict
 
32
from magicicada.gui.gtk.listings import (
 
33
    FoldersButton,
 
34
    PublicFilesButton,
 
35
    SharesToMeButton,
 
36
    SharesToOthersButton,
 
37
)
 
38
 
 
39
 
 
40
UBUNTU_ONE_ROOT = os.path.expanduser(u'~/Ubuntu One')
 
41
 
 
42
CONNECT = _(u'Connect')
 
43
CONNECTED = _(u'Connected')
 
44
CONNECTING = _(u'Connecting')
 
45
DISCONNECT = _(u'Disconnect')
 
46
DISCONNECTED = _(u'Disconnected')
 
47
DISCONNECTING = _(u'Disconnecting')
 
48
ERROR = _(u'Oops!')
 
49
IDLE = _(u'All done!')
 
50
START = _(u'Start')
 
51
STARTED = _(u'Started')
 
52
STARTING = _(u'Starting')
 
53
STOP = _(u'Stop')
 
54
STOPPED = _(u'Not running')
 
55
STOPPING = _(u'Stopping')
 
56
WORKING = _(u'Working')
 
57
 
 
58
logger = logging.getLogger('magicicada.gui.gtk.status')
 
59
 
 
60
ACTION_MAP = {
 
61
    syncdaemon.STATE_CONNECTING: (CONNECTING, DISCONNECT),
 
62
    syncdaemon.STATE_DISCONNECTED: (DISCONNECTED, CONNECT),
 
63
    syncdaemon.STATE_IDLE: (IDLE, DISCONNECT),
 
64
    syncdaemon.STATE_STARTING: (STARTING, STOP),
 
65
    syncdaemon.STATE_STOPPED: (STOPPED, START),
 
66
    syncdaemon.STATE_WORKING: (WORKING, DISCONNECT),
 
67
}
 
68
 
 
69
 
 
70
class MetadataDialog(Buildable):
 
71
    """The metadata dialog."""
 
72
 
 
73
    filename = 'metadata.ui'
 
74
 
 
75
    changed_message = {
 
76
        syncdaemon.CHANGED_NONE: (u'Synchronized', gtk.STOCK_APPLY),
 
77
        syncdaemon.CHANGED_SERVER: (u'With server changes, downloading',
 
78
                                    gtk.STOCK_GO_DOWN),
 
79
        syncdaemon.CHANGED_LOCAL: (u'With local changes, uploading',
 
80
                                   gtk.STOCK_GO_UP),
 
81
    }
 
82
 
 
83
    def run(self):
 
84
        """Run the dialog."""
 
85
        self.dialog.show()
 
86
 
 
87
    def got_metadata(self, path, data):
 
88
        """Activate the information elements and hide the spinner."""
 
89
        # stop and hide the spinner
 
90
        self.spinner.stop()
 
91
        self.spinner.hide()
 
92
 
 
93
        # title with crude path
 
94
        self.dialog.set_title("Metadata for %s" % (path,))
 
95
 
 
96
        # the icon is taken from crude path, no matter if synched or not
 
97
        if os.path.isdir(path):
 
98
            icon = gtk.STOCK_DIRECTORY
 
99
        else:
 
100
            icon = gtk.STOCK_FILE
 
101
        self.filetype_image.set_from_stock(icon, gtk.ICON_SIZE_MENU)
 
102
 
 
103
        # set the data in the elements
 
104
        if data == NOT_SYNCHED_PATH:
 
105
            # metadata path doesn't exist for syncdaemon, show crude path,
 
106
            # error message, and quit
 
107
            self.path_label.set_text(path)
 
108
            self.filepath_hbox.show()
 
109
            self.basic_info_label.set_text(NOT_SYNCHED_PATH)
 
110
            self.basic_info_label.show()
 
111
            return
 
112
 
 
113
        # show the nice path
 
114
        self.path_label.set_text(data['path'])
 
115
        self.filepath_hbox.show()
 
116
 
 
117
        # prepare simple text
 
118
        simple = []
 
119
        stat = data['stat']
 
120
        if stat is not None:
 
121
            size = stat['st_size']
 
122
            try:
 
123
                size = humanize_bytes(size)
 
124
            except (ValueError, TypeError):
 
125
                logger.exception('Error while humanizing bytes')
 
126
            simple.append("Size: %s" % (size,))
 
127
            tstamp = stat['st_mtime']
 
128
            simple.append("Modified on %s" % (time.ctime(tstamp),))
 
129
 
 
130
        # set state message and image
 
131
        state, stock = self.changed_message.get(data['changed'],
 
132
                                                ('Unknown state', None))
 
133
        simple.append(state)
 
134
        simple_text = "\n".join(simple)
 
135
        if stock is None:
 
136
            self.state_image.hide()
 
137
        else:
 
138
            self.state_image.set_from_stock(stock, gtk.ICON_SIZE_LARGE_TOOLBAR)
 
139
        self.state_hbox.show()
 
140
 
 
141
        # prepare detailed text
 
142
        raw = data['raw_result']
 
143
        detailed_text = '\n'.join('%s: %s' % i for i in raw.iteritems())
 
144
 
 
145
        # fill and show
 
146
        self.basic_info_label.set_text(simple_text)
 
147
        self.basic_info_label.show()
 
148
        self.detailed_info_textview.get_buffer().set_text(detailed_text)
 
149
        self.details_expander.show()
 
150
 
 
151
    def on_dialog_close(self, widget, data=None):
 
152
        """Close the dialog."""
 
153
        self.dialog.hide()
 
154
 
 
155
    def destroy(self):
 
156
        """Destroy this widget's dialog."""
 
157
        self.dialog.destroy()
 
158
 
 
159
 
 
160
class Status(Buildable, gtk.Alignment):
 
161
    """The toolbar with info."""
 
162
 
 
163
    filename = 'status.ui'
 
164
    logger = logger
 
165
    _u1_root = UBUNTU_ONE_ROOT
 
166
 
 
167
    def __init__(self, syncdaemon_instance=None, **kwargs):
 
168
        """Init."""
 
169
        Buildable.__init__(self)
 
170
        gtk.Alignment.__init__(self, **kwargs)
 
171
 
 
172
        if syncdaemon_instance is not None:
 
173
            self.sd = syncdaemon_instance
 
174
        else:
 
175
            self.sd = syncdaemon.SyncDaemon()
 
176
        self.sd.on_metadata_ready_callback = self.on_metadata_ready
 
177
 
 
178
        self._sd_actions = {
 
179
            CONNECT: (self.sd.connect, DISCONNECT),
 
180
            DISCONNECT: (self.sd.disconnect, CONNECT),
 
181
            START: (self.sd.start, STOP),
 
182
            STOP: (self.sd.quit, START),
 
183
        }
 
184
        self._metadata_dialogs = {}
 
185
        self._status_images = build_icon_dict(48)
 
186
 
 
187
        folders = FoldersButton(syncdaemon_instance=self.sd)
 
188
        shares_to_me = SharesToMeButton(syncdaemon_instance=self.sd)
 
189
        shares_to_others = SharesToOthersButton(syncdaemon_instance=self.sd)
 
190
        self.public_files = PublicFilesButton(syncdaemon_instance=self.sd)
 
191
 
 
192
        buttons = (folders, shares_to_me, shares_to_others, self.public_files)
 
193
        for button in buttons:
 
194
            self.toolbar.insert(button, -1)
 
195
        self.toolbar.set_sensitive(False)
 
196
 
 
197
        self.action_button.set_use_stock(True)
 
198
 
 
199
        self.update()
 
200
 
 
201
        self.add(self.info)
 
202
        self.show_all()
 
203
 
 
204
    # custom
 
205
 
 
206
    def _update_action_button(self, action):
 
207
        """Update the action button according to the SD state."""
 
208
        self.action_button.set_label(action)
 
209
        self.action_button.set_sensitive(True)
 
210
 
 
211
    # GTK callbacks
 
212
 
 
213
    @defer.inlineCallbacks
 
214
    def on_action_button_clicked(self, button):
 
215
        """An action was clicked by the user."""
 
216
        self.action_button.set_sensitive(False)
 
217
 
 
218
        sd_action, next_action = self._sd_actions[button.get_label()]
 
219
        yield sd_action()
 
220
 
 
221
        self._update_action_button(next_action)
 
222
 
 
223
    def on_metadata_clicked(self, widget, data=None):
 
224
        """Show metadata for a path choosen by the user."""
 
225
        res = self.file_chooser.run()
 
226
        self.file_chooser.hide()
 
227
        if res != gtk.FILE_CHOOSER_ACTION_OPEN:
 
228
            return
 
229
 
 
230
        path = self.file_chooser.get_filename()
 
231
        assert path is not None
 
232
 
 
233
        dialog = MetadataDialog()
 
234
        self._metadata_dialogs[path] = dialog
 
235
        self.sd.get_metadata(path)
 
236
        dialog.run()
 
237
 
 
238
    def on_file_chooser_open_clicked(self, widget, data=None):
 
239
        """Close the file_chooser dialog."""
 
240
        self.file_chooser.response(gtk.FILE_CHOOSER_ACTION_OPEN)
 
241
 
 
242
    def on_file_chooser_show(self, widget, data=None):
 
243
        """Close the file_chooser dialog."""
 
244
        self.file_chooser.set_current_folder(self._u1_root)
 
245
 
 
246
    # SyncDaemon callbacks
 
247
 
 
248
    @log(logger, level=logging.INFO)
 
249
    def on_initial_data_ready(self):
 
250
        """Initial data is now available in syncdaemon."""
 
251
        self.toolbar.set_sensitive(True)
 
252
        self.public_files.set_sensitive(False)
 
253
 
 
254
    @log(logger, level=logging.INFO)
 
255
    def on_initial_online_data_ready(self):
 
256
        """Online initial data is now available in syncdaemon."""
 
257
        self.public_files.set_sensitive(True)
 
258
 
 
259
    @log(logger)
 
260
    def on_metadata_ready(self, path, metadata):
 
261
        """Lower layer has the requested metadata for 'path'."""
 
262
        if path not in self._metadata_dialogs:
 
263
            logger.info("on_metadata_ready: path %r not in stored paths!",
 
264
                        path)
 
265
            return
 
266
 
 
267
        dialog = self._metadata_dialogs[path]
 
268
        dialog.got_metadata(path, metadata)
 
269
 
 
270
    def update(self, *args, **kwargs):
 
271
        """Update UI based on SD current state."""
 
272
        current_state = self.sd.current_state
 
273
        logger.debug('updating UI with state %r', current_state)
 
274
 
 
275
        state = current_state.state
 
276
        status, next_action = ACTION_MAP[state]
 
277
        if state == syncdaemon.STATE_IDLE:
 
278
            self.status_image.set_from_pixbuf(self._status_images['idle'])
 
279
        elif state in (syncdaemon.STATE_CONNECTING, syncdaemon.STATE_STARTING,
 
280
                       syncdaemon.STATE_WORKING):
 
281
            self.status_image.set_from_pixbuf(self._status_images['working'])
 
282
        else:
 
283
            self.status_image.set_from_pixbuf(self._status_images['alert'])
 
284
 
 
285
        self._update_action_button(next_action)
 
286
        self.status_label.set_text(status)