~ubuntu-branches/ubuntu/lucid/landscape-client/lucid-updates

« back to all changes in this revision

Viewing changes to landscape/ui/view/configuration.py

  • Committer: Package Import Robot
  • Author(s): Andreas Hasenack
  • Date: 2012-04-10 14:28:48 UTC
  • mfrom: (1.1.27)
  • mto: This revision was merged to the branch mainline in revision 35.
  • Revision ID: package-import@ubuntu.com-20120410142848-7xsy4g2xii7y7ntc
ImportĀ upstreamĀ versionĀ 12.04.3

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
import re
 
2
import os
 
3
 
 
4
from gettext import gettext as _
 
5
 
 
6
from gi.repository import GObject, Gtk
 
7
 
 
8
from landscape.ui.constants import (
 
9
    CANONICAL_MANAGED, LOCAL_MANAGED, NOT_MANAGED)
 
10
 
 
11
# Note, I think this may not be fully compliant with the changes in RFC 1123
 
12
HOST_NAME_REGEXP = re.compile("^(([a-zA-Z][a-zA-Z0-9\-]*)?[a-zA-Z0-9][\.]?)*"
 
13
                              "(([A-Za-z][A-Za-z0-9\-]*)?[A-Za-z0-9])$")
 
14
 
 
15
 
 
16
def sanitise_host_name(host_name):
 
17
    """
 
18
    Do some minimal host name sanitation.
 
19
    """
 
20
    return host_name.strip()
 
21
 
 
22
 
 
23
def is_valid_host_name(host_name):
 
24
    """
 
25
    Check that the provided host name complies with L{HOST_NAME_REGEXP} and is
 
26
    therefor valid.
 
27
    """
 
28
    return HOST_NAME_REGEXP.match(host_name) is not None
 
29
 
 
30
 
 
31
def is_ascii(text):
 
32
    """
 
33
    Test that the provided string contains only characters from the ASCII set.
 
34
    """
 
35
    try:
 
36
        text.decode("ascii")
 
37
        return True
 
38
    except UnicodeDecodeError:
 
39
        return False
 
40
 
 
41
 
 
42
class ClientSettingsDialog(Gtk.Dialog):
 
43
    """
 
44
    L{ClientSettingsDialog} is a subclass of Gtk.Dialog that loads the UI
 
45
    components from the associated Glade XML file and wires everything up to
 
46
    the controller.
 
47
    """
 
48
 
 
49
    GLADE_FILE = "landscape-client-settings.glade"
 
50
    INVALID_HOST_NAME = 0
 
51
    UNICODE_IN_ENTRY = 1
 
52
 
 
53
    def __init__(self, controller):
 
54
        super(ClientSettingsDialog, self).__init__(
 
55
            title=_("Management Service"),
 
56
            flags=Gtk.DialogFlags.MODAL)
 
57
        self.set_default_icon_name("preferences-management-service")
 
58
        self.set_resizable(False)
 
59
        self._initialised = False
 
60
        self._validation_errors = set()
 
61
        self._errored_entries = []
 
62
        self.controller = controller
 
63
        self.setup_ui()
 
64
        self.load_data()
 
65
        # One extra revert to reset after loading data
 
66
        self.controller.revert()
 
67
 
 
68
    def indicate_error_on_entry(self, entry):
 
69
        """
 
70
        Show a warning icon on a L{Gtk.Entry} to indicate some associated
 
71
        error.
 
72
        """
 
73
        entry.set_icon_from_stock(
 
74
            Gtk.EntryIconPosition.PRIMARY, Gtk.STOCK_DIALOG_WARNING)
 
75
        self._errored_entries.append(entry)
 
76
 
 
77
    def check_local_landscape_host_name_entry(self):
 
78
        host_name = sanitise_host_name(
 
79
            self.local_landscape_host_entry.get_text())
 
80
        ascii_ok = is_ascii(host_name)
 
81
        host_name_ok = is_valid_host_name(host_name)
 
82
        if ascii_ok and host_name_ok:
 
83
            self.local_landscape_host_entry.set_text(host_name)
 
84
            return True
 
85
        else:
 
86
            self.indicate_error_on_entry(self.local_landscape_host_entry)
 
87
            if not host_name_ok:
 
88
                self._validation_errors.add(self.INVALID_HOST_NAME)
 
89
            if not ascii_ok:
 
90
                self._validation_errors.add(self.UNICODE_IN_ENTRY)
 
91
            return False
 
92
 
 
93
    def check_entry(self, entry):
 
94
        """
 
95
        Check that the text content of a L{Gtk.Entry} is acceptable.
 
96
        """
 
97
        if is_ascii(entry.get_text()):
 
98
            return True
 
99
        else:
 
100
            self.indicate_error_on_entry(entry)
 
101
            self._validation_errors.add(self.UNICODE_IN_ENTRY)
 
102
            return False
 
103
 
 
104
    def validity_check(self):
 
105
        self._validation_errors = set()
 
106
        if self._info_bar_container.get_visible():
 
107
            self.dismiss_infobar(None)
 
108
        active_iter = self.liststore.get_iter(
 
109
            self.use_type_combobox.get_active())
 
110
        [management_type] = self.liststore.get(active_iter, 0)
 
111
        if management_type == NOT_MANAGED:
 
112
            return True
 
113
        elif management_type == CANONICAL_MANAGED:
 
114
            account_name_ok = self.check_entry(self.hosted_account_name_entry)
 
115
            password_ok = self.check_entry(self.hosted_password_entry)
 
116
            return account_name_ok and password_ok
 
117
        else:
 
118
            host_name_ok = self.check_local_landscape_host_name_entry()
 
119
            password_ok = self.check_entry(self.local_password_entry)
 
120
            return host_name_ok and password_ok
 
121
 
 
122
    @property
 
123
    def NO_SERVICE_TEXT(self):
 
124
        return _("None")
 
125
 
 
126
    @property
 
127
    def HOSTED_SERVICE_TEXT(self):
 
128
        return _("Landscape - hosted by Canonical")
 
129
 
 
130
    @property
 
131
    def LOCAL_SERVICE_TEXT(self):
 
132
        return _("Landscape - dedicated server")
 
133
 
 
134
    @property
 
135
    def REGISTER_BUTTON_TEXT(self):
 
136
        return _("Register")
 
137
 
 
138
    @property
 
139
    def DISABLE_BUTTON_TEXT(self):
 
140
        return _("Disable")
 
141
 
 
142
    @property
 
143
    def INVALID_HOST_NAME_MESSAGE(self):
 
144
        return _("Invalid host name.")
 
145
 
 
146
    @property
 
147
    def UNICODE_IN_ENTRY_MESSAGE(self):
 
148
        return _("Only ASCII characters are allowed.")
 
149
 
 
150
    def _set_use_type_combobox_from_controller(self):
 
151
        """
 
152
        Load the persisted L{management_type} from the controller and set the
 
153
        combobox appropriately.
 
154
 
 
155
        Note that Gtk makes us jump through some hoops by having it's own model
 
156
        level to deal with here.  The conversion between paths and iters makes
 
157
        more sense if you understand that treeviews use the same model.
 
158
        """
 
159
        list_iter = self.liststore.get_iter_first()
 
160
        while (self.liststore.get(list_iter, 0)[0] !=
 
161
               self.controller.management_type):
 
162
            list_iter = self.liststore.iter_next(list_iter)
 
163
        path = self.liststore.get_path(list_iter)
 
164
        [index] = path.get_indices()
 
165
        self.use_type_combobox.set_active(index)
 
166
 
 
167
    def _set_hosted_values_from_controller(self):
 
168
        self.hosted_account_name_entry.set_text(
 
169
            self.controller.hosted_account_name)
 
170
        self.hosted_password_entry.set_text(self.controller.hosted_password)
 
171
 
 
172
    def _set_local_values_from_controller(self):
 
173
        self.local_landscape_host_entry.set_text(
 
174
            self.controller.local_landscape_host)
 
175
        self.local_password_entry.set_text(self.controller.local_password)
 
176
 
 
177
    def load_data(self):
 
178
        self._initialised = False
 
179
        self.controller.load()
 
180
        self._set_hosted_values_from_controller()
 
181
        self._set_local_values_from_controller()
 
182
        self._set_use_type_combobox_from_controller()
 
183
        self._initialised = True
 
184
 
 
185
    def make_liststore(self):
 
186
        """
 
187
        Construct the correct L{Gtk.ListStore} to drive the L{Gtk.ComboBox} for
 
188
        use-type.  This a table of:
 
189
 
 
190
           * Management type (key)
 
191
           * Text to display in the combobox
 
192
           * L{Gtk.Frame} to load when this item is selected.
 
193
        """
 
194
        liststore = Gtk.ListStore(GObject.TYPE_PYOBJECT,
 
195
                                  GObject.TYPE_STRING,
 
196
                                  GObject.TYPE_PYOBJECT)
 
197
        self.active_widget = None
 
198
        liststore.append([NOT_MANAGED, self.NO_SERVICE_TEXT,
 
199
                          self._builder.get_object("no-service-frame")])
 
200
        liststore.append([CANONICAL_MANAGED, self.HOSTED_SERVICE_TEXT,
 
201
                          self._builder.get_object("hosted-service-frame")])
 
202
        liststore.append([LOCAL_MANAGED, self.LOCAL_SERVICE_TEXT,
 
203
                          self._builder.get_object("local-service-frame")])
 
204
        return liststore
 
205
 
 
206
    def link_hosted_service_widgets(self):
 
207
        self.hosted_account_name_entry = self._builder.get_object(
 
208
            "hosted-account-name-entry")
 
209
        self.hosted_account_name_entry.connect(
 
210
            "changed", self.on_changed_event, "hosted_account_name")
 
211
 
 
212
        self.hosted_password_entry = self._builder.get_object(
 
213
            "hosted-password-entry")
 
214
        self.hosted_password_entry.connect(
 
215
            "changed", self.on_changed_event, "hosted_password")
 
216
 
 
217
    def link_local_service_widgets(self):
 
218
        self.local_landscape_host_entry = self._builder.get_object(
 
219
            "local-landscape-host-entry")
 
220
        self.local_landscape_host_entry.connect(
 
221
            "changed", self.on_changed_event, "local_landscape_host")
 
222
 
 
223
        self.local_password_entry = self._builder.get_object(
 
224
            "local-password-entry")
 
225
        self.local_password_entry.connect(
 
226
            "changed", self.on_changed_event, "local_password")
 
227
 
 
228
    def link_use_type_combobox(self, liststore):
 
229
        self.use_type_combobox = self._builder.get_object("use-type-combobox")
 
230
        self.use_type_combobox.connect("changed", self.on_combo_changed)
 
231
        self.use_type_combobox.set_model(liststore)
 
232
        cell = Gtk.CellRendererText()
 
233
        self.use_type_combobox.pack_start(cell, True)
 
234
        self.use_type_combobox.add_attribute(cell, 'text', 1)
 
235
 
 
236
    def cancel_response(self, widget):
 
237
        self.response(Gtk.ResponseType.CANCEL)
 
238
 
 
239
    def register_response(self, widget):
 
240
        if self.validity_check():
 
241
            self.response(Gtk.ResponseType.OK)
 
242
        else:
 
243
            error_text = []
 
244
            if self.UNICODE_IN_ENTRY in self._validation_errors:
 
245
                error_text.append(self.UNICODE_IN_ENTRY_MESSAGE)
 
246
            if self.INVALID_HOST_NAME in self._validation_errors:
 
247
                error_text.append(self.INVALID_HOST_NAME_MESSAGE)
 
248
            self.info_message.set_text("\n".join(error_text))
 
249
            self._info_bar_container.show()
 
250
 
 
251
    def set_button_text(self, management_type):
 
252
        [alignment] = self.register_button.get_children()
 
253
        [hbox] = alignment.get_children()
 
254
        [image, label] = hbox.get_children()
 
255
        if management_type == NOT_MANAGED:
 
256
            label.set_text(self.DISABLE_BUTTON_TEXT)
 
257
        else:
 
258
            label.set_text(self.REGISTER_BUTTON_TEXT)
 
259
 
 
260
    def setup_buttons(self):
 
261
        self.revert_button = Gtk.Button(stock=Gtk.STOCK_REVERT_TO_SAVED)
 
262
        self.action_area.pack_start(self.revert_button, True, True, 0)
 
263
        self.revert_button.connect("clicked", self.revert)
 
264
        self.revert_button.show()
 
265
        self.cancel_button = Gtk.Button(stock=Gtk.STOCK_CANCEL)
 
266
        self.action_area.pack_start(self.cancel_button, True, True, 0)
 
267
        self.cancel_button.show()
 
268
        self.cancel_button.connect("clicked", self.cancel_response)
 
269
        self.register_button = Gtk.Button(stock=Gtk.STOCK_OK)
 
270
        self.action_area.pack_start(self.register_button, True, True, 0)
 
271
        self.register_button.show()
 
272
        self.register_button.connect("clicked", self.register_response)
 
273
 
 
274
    def dismiss_infobar(self, widget):
 
275
        self._info_bar_container.hide()
 
276
        for entry in self._errored_entries:
 
277
            entry.set_icon_from_stock(Gtk.EntryIconPosition.PRIMARY, None)
 
278
        self._errored_entries = []
 
279
 
 
280
    def setup_info_bar(self):
 
281
        labels_size_group = self._builder.get_object("labels-sizegroup")
 
282
        entries_size_group = self._builder.get_object("entries-sizegroup")
 
283
        labels_size_group.set_ignore_hidden(False)
 
284
        entries_size_group.set_ignore_hidden(False)
 
285
        self._info_bar_container = Gtk.HBox()
 
286
        self._info_bar_container.set_spacing(12)
 
287
        info_bar = Gtk.InfoBar()
 
288
        entries_size_group.add_widget(info_bar)
 
289
        info_bar.show()
 
290
        empty_label = Gtk.Label()
 
291
        labels_size_group.add_widget(empty_label)
 
292
        empty_label.show()
 
293
        self._info_bar_container.pack_start(empty_label, expand=False,
 
294
                                            fill=False, padding=0)
 
295
        self._info_bar_container.pack_start(info_bar, expand=False, fill=False,
 
296
                                            padding=0)
 
297
        content_area = info_bar.get_content_area()
 
298
        hbox = Gtk.HBox()
 
299
        self.info_message = Gtk.Label()
 
300
        self.info_message.set_alignment(0, 0.5)
 
301
        self.info_message.show()
 
302
        hbox.pack_start(self.info_message, expand=True, fill=True, padding=6)
 
303
        ok_button = Gtk.Button("Dismiss")
 
304
        ok_button.connect("clicked", self.dismiss_infobar)
 
305
        ok_button.show()
 
306
        hbox.pack_start(ok_button, expand=True, fill=True, padding=0)
 
307
        hbox.show()
 
308
        content_area.pack_start(hbox, expand=True, fill=True, padding=0)
 
309
 
 
310
    def setup_ui(self):
 
311
        self._builder = Gtk.Builder()
 
312
        self._builder.set_translation_domain("landscape-client")
 
313
        self._builder.add_from_file(
 
314
            os.path.join(
 
315
                os.path.dirname(__file__), "ui", self.GLADE_FILE))
 
316
        content_area = self.get_content_area()
 
317
        content_area.set_spacing(12)
 
318
        self.set_border_width(12)
 
319
        self.setup_info_bar()
 
320
        self._vbox = self._builder.get_object("toplevel-vbox")
 
321
        self._vbox.unparent()
 
322
        content_area.pack_start(self._vbox, expand=True, fill=True,
 
323
                                padding=12)
 
324
        self._vbox.pack_start(self._info_bar_container, expand=False,
 
325
                              fill=False, padding=0)
 
326
        self.liststore = self.make_liststore()
 
327
        self.link_use_type_combobox(self.liststore)
 
328
        self.link_hosted_service_widgets()
 
329
        self.link_local_service_widgets()
 
330
        self.setup_buttons()
 
331
 
 
332
    def on_combo_changed(self, combobox):
 
333
        list_iter = self.liststore.get_iter(combobox.get_active())
 
334
        if not self.active_widget is None:
 
335
            self._vbox.remove(self.active_widget)
 
336
        [management_type] = self.liststore.get(list_iter, 0)
 
337
        self.set_button_text(management_type)
 
338
        if self._initialised:
 
339
            self.controller.management_type = management_type
 
340
            self.controller.modify()
 
341
        [self.active_widget] = self.liststore.get(list_iter, 2)
 
342
        self.active_widget.unparent()
 
343
        self._vbox.add(self.active_widget)
 
344
 
 
345
    def on_changed_event(self, widget, attribute):
 
346
        setattr(self.controller, attribute, widget.get_text())
 
347
        self.controller.modify()
 
348
 
 
349
    def quit(self, *args):
 
350
        self.destroy()
 
351
 
 
352
    def revert(self, button):
 
353
        self.controller.revert()
 
354
        self.load_data()
 
355
        # One extra revert to reset after loading data
 
356
        self.controller.revert()