c21b5a9e3a53a6f74d3b6a03bafbac7e82ea32d2
[meabook] / ui / hildon_ui.py
1 """
2 Hildon UI for Meabook
3 """
4
5 import gtk
6 import hildon
7 import gobject
8 from gettext import gettext as _
9 from meabook.constants import *
10 from meabook.ui.ui import MeabookUI
11
12
13 def create_button(title, value):
14     """Create HildonButton."""
15
16     button = hildon.Button(gtk.HILDON_SIZE_AUTO | \
17         gtk.HILDON_SIZE_FINGER_HEIGHT, \
18         hildon.BUTTON_ARRANGEMENT_VERTICAL, title, value)
19     button.set_style(hildon.BUTTON_STYLE_PICKER)
20     button.set_alignment(0, 0, 0, 0)
21     return button
22
23
24 def create_menu_button(title):
25     """Create HildonMenu button."""
26
27     return hildon.Button(gtk.HILDON_SIZE_AUTO, \
28         hildon.BUTTON_ARRANGEMENT_HORIZONTAL, title)
29
30
31 def set_selector_content(selector, handler, items=[]):
32     """Updates selector widget content."""
33
34     selector.handler_block(handler) # temporary block handler
35     # setting new content
36     # model: name, internal_name, type
37     model = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_INT, \
38         gobject.TYPE_STRING)
39     for name, internal_name, item_type in items:
40         model.append((name, internal_name, item_type))
41     if selector.get_num_columns():
42         selector.remove_column(0)
43     selector.append_text_column(model, False)
44     selector.unselect_all(0)
45     selector.handler_unblock(handler) # reconnect callback
46
47
48 def set_box_content(box, handler, items=[]):
49     """Updates box widget content."""
50
51     def on_button_click(widget):
52         handler(widget.get_title(), widget.get_data('iname'))
53
54     for child in box.get_children():
55         box.remove(child)
56     for title, value, internal_name, item_type in items:
57         button = create_button(title, value)
58         button.connect('clicked', on_button_click)
59         button.set_data('iname', internal_name)
60         button.set_relief(gtk.RELIEF_NONE)
61         button.show()
62         box.pack_start(button, expand=False)
63
64
65
66 class HildonMeabook(MeabookUI):
67     def __init__(self, controller, renderer, config):
68         MeabookUI.__init__(self, controller, renderer, config)
69         self.handler = None
70         self.window = hildon.StackableWindow()
71         self.window.set_title(_('Meabook'))
72         self.window.connect('destroy', self.exit)
73         # create menu buttons
74         menu = hildon.AppMenu()
75         settings_button = create_menu_button(_('Settings'))
76         about_button = create_menu_button(_('About'))
77         import_button = create_menu_button(_('Import'))
78         search_button = create_menu_button(_('Search'))
79         # create filter widgets and connect signals
80         self.level1_filter = gtk.RadioButton(None, _('level1'))
81         self.level2_filter = gtk.RadioButton(self.level1_filter, _('level2'))
82         self.level3_filter = gtk.RadioButton(self.level2_filter, _('level3'))
83         for filter_widget in [self.level1_filter, self.level2_filter, \
84             self.level3_filter]:
85             filter_widget.set_mode(False)
86             filter_widget.connect('toggled', self.apply_filter_cb)
87         # create items widgets
88         self.selector = hildon.TouchSelector()
89         self.box_container = hildon.PannableArea()
90         self.box = gtk.VBox()
91         # create search widgets
92         widgets_box = gtk.VBox()
93         self.search_widgets_box = gtk.HBox()
94         self.search_entry = hildon.Entry(gtk.HILDON_SIZE_AUTO | \
95             gtk.HILDON_SIZE_FINGER_HEIGHT)
96         clear_search_entry_container = gtk.EventBox()
97         clear_search_entry_image = gtk.image_new_from_icon_name( \
98             'general_delete', gtk.HILDON_SIZE_FINGER_HEIGHT)
99         # connect signals
100         settings_button.connect('clicked', self.show_settings_dialog_cb)
101         about_button.connect('clicked', self.show_about_dialog_cb)
102         import_button.connect('clicked', self.show_import_dialog_cb)
103         search_button.connect('clicked', self.show_search_dialog_cb)
104         self.handler = self.selector.connect('changed', self.select_item_cb)
105         self.search_entry.connect('key-release-event', self.search_item_cb)
106         clear_search_entry_container.connect('button-press-event', \
107             self.clear_search_entry_cb)
108         # packing widgets
109         # packing search widgets
110         clear_search_entry_container.add(clear_search_entry_image)
111         self.search_widgets_box.pack_start(self.search_entry, expand=True)
112         self.search_widgets_box.pack_start(clear_search_entry_container, \
113             expand=False, padding=24)
114         # packing items widgets
115         self.box_container.add_with_viewport(self.box)
116         widgets_box.pack_start(self.selector, expand=True)
117         widgets_box.pack_start(self.box_container, expand=True)
118         widgets_box.pack_end(self.search_widgets_box, expand=False)
119         self.window.add(widgets_box)
120         # packing menu widgets
121         menu.add_filter(self.level1_filter)
122         menu.add_filter(self.level2_filter)
123         menu.add_filter(self.level3_filter)
124         menu.append(settings_button)
125         menu.append(import_button)
126         menu.append(search_button)
127         menu.append(about_button)
128         menu.show_all()
129         self.window.set_app_menu(menu)
130         self.window.show_all()
131
132     def _show_ui(self, view='selector', show_search=False):
133         """Shows necessary widgets for selected view."""
134
135         if view == 'selector':
136             self.box_container.hide()
137             self.selector.show()
138         elif view == 'box':
139             self.selector.hide()
140             self.box_container.show_all()
141         else:
142             self.selector.hide()
143             self.box_container.hide()
144         if show_search:
145             self.search_entry.set_text('')
146             self.search_entry.set_placeholder(_('Enter search text here'))
147             self.search_widgets_box.show_all()
148         else:
149             self.search_widgets_box.hide()
150
151     def _unfreeze_ui(self):
152         while gtk.events_pending():
153             gtk.main_iteration(False)
154
155     def _update_title(self, title):
156         if title is not None:
157             self.window.set_title(title)
158
159     def _show_items_dialog(self, title, items, touch_selector_view=True):
160         """Creates dialog with items. If 'touch_selector_view' is True,
161         then items will be shown in TouchSelector widget, else each item
162         will be shown as Button in PannableArea."""
163
164         window = hildon.StackableWindow()
165         window.set_title(title)
166         if touch_selector_view:
167             container = hildon.TouchSelector()
168             handler = container.connect('changed', self.select_item_cb)
169             set_selector_content(container, handler, items)
170         else:
171             widgets_box = gtk.VBox()
172             container = hildon.PannableArea()
173             container.add_with_viewport(items_box)
174             set_box_content(widgets_box, self._show_item_dialog, items)
175         window.add(container)
176         window.show_all()
177
178     def _show_item_dialog(self, title, entry_id):
179         """Shows detailed item information."""
180
181         def show_settings_dialog(widget, parent, func1, func2, entry_id):
182             dialog = ConfigurationDialog(self.controller, self.config)
183             response = getattr(dialog, func1)(None, parent)
184             if response == gtk.RESPONSE_OK:
185                 func2(parent, entry_id)
186
187         def update_entry(window, entry_id):
188             # create widgets
189             entry = self.controller.get_item(entry_id)
190             widgets_table = gtk.Table(rows=1, columns=1)
191             info_box = gtk.VBox()
192             image_box = gtk.VBox()
193             pannable_area = hildon.PannableArea()
194             pannable_area.set_property('mov-mode', hildon.MOVEMENT_MODE_BOTH)
195             image = self.renderer.render_image(dict(entry))
196             for fname, fvalue in entry:
197                 if fname == 'image':
198                     continue
199                 button = self.renderer.render_button(_(fname) , fvalue, fname)
200                 info_box.pack_start(button, expand=False)
201             # pack widgets
202             image_box.pack_start(image, expand=False)
203             widgets_table.attach(image_box, 0, 1, 0, 1, xoptions=gtk.FILL|gtk.SHRINK, \
204                 yoptions=gtk.FILL, xpadding=14, ypadding=8)
205             pannable_area.add_with_viewport(info_box)
206             widgets_table.attach(pannable_area, 1, 2, 0, 1, ypadding=8)
207             child = window.get_child()
208             if child:
209                 child.destroy()
210             window.add(widgets_table)
211             widgets_table.show_all()
212
213
214         # create widgets
215         window = hildon.StackableWindow()
216         window.set_title(title)
217         menu = hildon.AppMenu()
218         fields_button = create_menu_button(_('Fields to show'))
219         order_button = create_menu_button(_('Fields order'))
220         fields_button.connect('clicked', show_settings_dialog, window, \
221             'show_fields_settings_cb', update_entry, entry_id)
222         order_button.connect('clicked', show_settings_dialog, window, \
223             'show_order_settings_cb', update_entry, entry_id)
224         menu.append(fields_button)
225         menu.append(order_button)
226
227         update_entry(window, entry_id)
228         window.set_app_menu(menu)
229         menu.show_all()
230         window.show_all()
231
232
233     # Implementation of Base UI interface
234     def start(self):
235         self.apply_filter_cb(self.level1_filter)
236         gtk.main()
237
238     def exit(self, event):
239         gtk.main_quit()
240         self.controller.stop()
241
242     def create_information_dialog(self, title, message):
243         dialog = hildon.Dialog()
244         dialog.set_title(title)
245         label = gtk.Label(message)
246         dialog.vbox.add(label)
247         dialog.vbox.show_all()
248         dialog.run()
249         dialog.destroy()
250
251     def create_about_dialog(self):
252         AboutDialog()
253
254     def create_import_dialog(self):
255         chooser = gobject.new(hildon.FileChooserDialog, \
256             action=gtk.FILE_CHOOSER_ACTION_OPEN)
257         chooser.set_property('show-files', True)
258         chooser.run()
259         path = chooser.get_filename()
260         chooser.destroy()
261         return path
262
263     def create_progress_dialog(self, title=None):
264         self._update_title(title)
265         self._show_ui('hide', False)
266         hildon.hildon_gtk_window_set_progress_indicator(self.window, 1)
267         self._unfreeze_ui()
268
269     def create_configuration_dialog(self, controller, config):
270         ConfigurationDialog(controller, config).run()
271
272     def create_search_dialog(self, controller):
273         dialog = SearchDialog(controller)
274         param = dialog.run()
275         if param:
276             print(param)
277
278
279     # Hildon UI callbacks
280     def show_about_dialog_cb(self, widget):
281         """Shows About Dialog."""
282
283         self.controller.show_about_dialog()
284
285     def show_import_dialog_cb(self, widget):
286         """Shows Import Dialog."""
287
288         if self.controller.show_import_dialog():
289             self._show_ui('selector')
290             self.apply_filter_cb(self.level1_filter)
291         hildon.hildon_gtk_window_set_progress_indicator(self.window, 0)
292
293     def show_settings_dialog_cb(self, widget):
294         """Shows Config dialog."""
295
296         self.controller.show_configuration_dialog()
297
298     def show_search_dialog_cb(self, widget):
299         """Show Search dialog."""
300
301         self.controller.show_search_dialog()
302
303     def apply_filter_cb(self, widget):
304         """Updates toplevel selector with different level items."""
305
306         if not widget.get_active():
307             return
308
309         self._update_title(' - '.join([_('Meabook'), widget.get_label()]))
310
311         if widget == self.level1_filter:
312             self._show_ui()
313             set_selector_content(self.selector, self.handler, \
314                 self.controller.get_all_folders())
315         elif widget == self.level2_filter:
316             self._show_ui()
317             set_selector_content(self.selector, self.handler, \
318                 self.controller.get_all_subfolders())
319         else:
320             self._show_ui(view='box', show_search=True)
321
322     def select_item_cb(self, widget, column):
323         """
324         Emits when changes selector content.
325         Opens new StackableWindow with new content.
326         """
327
328         item_name, internal_name, item_type = \
329             widget.get_model(0)[widget.get_active(0)]
330         if item_type == TYPE_DIRECTORY:
331             self._show_items_dialog(item_name, self.controller.get_items( \
332                 internal_name))
333         else:
334             self._show_item_dialog(item_name, internal_name)
335
336     def search_item_cb(self, widget, event):
337         """Search items from database."""
338
339         set_box_content(self.box, self._show_item_dialog, \
340             self.controller.get_files_by_pattern(widget.get_text(), \
341             separated=True))
342         widget.grab_focus()
343
344     def clear_search_entry_cb(self, widget, event):
345         """Clears search entry content."""
346
347         self.search_entry.set_text('')
348         set_box_content(self.box, None)
349
350
351
352 class AboutDialog(hildon.Dialog):
353     """Hildon About Dialog."""
354
355     def __init__(self):
356         from meabook.version import version
357         hildon.Dialog.__init__(self)
358         self.set_title(_('About'))
359         info_label = gtk.Label()
360         info_label.set_use_markup(True)
361         info_label.set_justify(gtk.JUSTIFY_CENTER)
362         info_label.set_markup("<span foreground='white' size='medium'><b>" \
363             "Meabook</b></span><span foreground='white' size='small'> - " \
364             "Enterprise address book</span>\n<span foreground='white' " \
365             "size='small'>Version %s</span>\n\n\n<span foreground='white'" \
366             "size='small'><b>Developers:</b></span>\n<span foreground=" \
367             "'white' size='small'>Tanya Makova | </span><span foreground=" \
368             "'#299BFC' size='small'>tanyshk@gmail.com</span>\n<span " \
369             "foreground='white' size='small'>Max Usachev | </span><span " \
370             "foreground='#299BFC' size='small'>maxusachev@gmail.com</span>" \
371             "\n" % version)
372         self.vbox.add(info_label)
373         self.vbox.show_all()
374         self.run()
375         self.destroy()
376
377
378
379 class SearchDialog:
380     """Hildon Search Dialog."""
381
382     def __init__(self, controller):
383         self.controller = controller
384
385     def run(self):
386         dialog = hildon.Dialog()
387         dialog.set_title(_('Search'))
388
389         selector = hildon.TouchSelector(text=True)
390         #for item in ('cn', 'mobile'):
391         for item in self.controller.get_fields():
392             selector.append_text(_(item))
393         selector.set_active(0, 0)
394
395         button = hildon.PickerButton(gtk.HILDON_SIZE_AUTO | \
396             gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
397         button.set_alignment(0, 0, 0, 0)
398         button.set_title(_("Search criterion"))
399         button.set_selector(selector)
400
401         entry = hildon.Entry(gtk.HILDON_SIZE_AUTO | gtk.HILDON_SIZE_FINGER_HEIGHT)
402         entry.set_placeholder(_("Enter search text here"))
403
404         dialog.vbox.pack_start(button, expand=False)
405         dialog.vbox.pack_end(entry, expand=False)
406         dialog.vbox.show_all()
407         dialog.add_button(_('Search'), gtk.RESPONSE_OK)
408         response = dialog.run()
409         if response == gtk.RESPONSE_OK:
410             result = (self.controller.get_localized_fields()[ \
411                 button.get_value().decode('utf-8')], entry.get_text())
412             dialog.destroy()
413             return result
414         dialog.destroy()
415         return (False, None)
416
417
418
419 class ConfigurationDialog:
420     """Hildon Configuration Dialog."""
421
422     def __init__(self, controller, config):
423         self.config = config
424         self.controller = controller
425
426     def run(self):
427         dialog = hildon.Dialog()
428         dialog.set_title(_('Settings'))
429
430         button_order = create_button(_('Fields order'), self._update_value( \
431             None, self.config.get_order()))
432         button_order.connect('clicked', self.show_order_settings_cb, dialog)
433         button_fields = create_button(_('Fields to show'), self._update_value( \
434             None, self.config.get_fields()))
435         button_fields.connect('clicked', self.show_fields_settings_cb, dialog)
436
437         dialog.vbox.pack_start(button_fields, expand=False)
438         dialog.vbox.pack_start(button_order, expand=False)
439         dialog.vbox.show_all()
440         dialog.run()
441         dialog.destroy()
442
443     def _update_value(self, widget, fields):
444         """Updates widget title."""
445
446         value = ', '.join([_(field) for field in fields])
447         if widget is not None:
448             widget.set_value(value)
449         return value
450
451     def show_fields_settings_cb(self, widget, parent):
452         """Shows dialog for selecting fields to show."""
453
454         dialog = hildon.PickerDialog(parent)
455         dialog.set_title(_('Fields to show'))
456         selector = hildon.TouchSelector(text=True)
457
458         fields = self.controller.get_fields()
459         # fill items list
460         for field in fields:
461             selector.append_text(_(field))
462
463         selector.set_column_selection_mode( \
464             hildon.TOUCH_SELECTOR_SELECTION_MODE_MULTIPLE)
465         selector.unselect_all(0)
466
467         # mark necessary fields
468         fields_to_select = self.config.get_fields()
469         model = selector.get_model(0)
470         for index, field in enumerate(fields):
471             if field in fields_to_select:
472                 selector.select_iter(0, model.get_iter(index), False)
473
474         dialog.set_selector(selector)
475         response = dialog.run()
476         if response == gtk.RESPONSE_OK:
477             model = selector.get_model(0)
478             selected_item_indexes = [index for index in [item[0] for item \
479                 in selector.get_selected_rows(0)]]
480             selected_fields = [fields[index] for index in selected_item_indexes]
481             self.config.set_fields(selected_fields)
482             self._update_value(widget, selected_fields)
483         dialog.destroy()
484         return response
485
486     def show_order_settings_cb(self, widget, parent):
487         """Shows dialog for setting fields order."""
488
489
490         def show_fields_chooser(widget, parent):
491             """Shows dialog to select field from fields list."""
492
493             dialog = hildon.PickerDialog(parent)
494             dialog.set_title(_('Fields'))
495             selector = hildon.TouchSelector(text=True)
496             dialog.set_selector(selector)
497             selector.set_column_selection_mode( \
498                 hildon.TOUCH_SELECTOR_SELECTION_MODE_SINGLE)
499             # fill fields list
500             for field in self.controller.get_fields():
501                 selector.append_text(_(field))
502             dialog.run()
503             widget.set_value(selector.get_current_text())
504             dialog.destroy()
505
506
507         dialog = hildon.Dialog()
508         dialog.set_title(_('Fields order'))
509         pannable_area = hildon.PannableArea()
510         pannable_area.set_size_request_policy(hildon.SIZE_REQUEST_CHILDREN)
511
512         vbox = gtk.VBox()
513         for index, field in enumerate(self.config.get_order()):
514             button = create_button(' '.join([_('Position'), str(index)]), \
515                 _(field))
516             button.connect('clicked', show_fields_chooser, dialog)
517             vbox.pack_start(button, expand=False)
518         pannable_area.add_with_viewport(vbox)
519         dialog.add_button(_('Done'), gtk.RESPONSE_OK)
520         dialog.vbox.pack_start(pannable_area)
521         dialog.vbox.show_all()
522         response = dialog.run()
523         if response == gtk.RESPONSE_OK:
524             fields_dict = self.controller.get_localized_fields()
525             new_ordered_fields = [fields_dict[button.get_value().decode( \
526                 'utf-8')] for button in vbox.get_children()]
527             self.config.set_order(new_ordered_fields)
528             self._update_value(widget, new_ordered_fields)
529         dialog.destroy()
530         return response