794130ebe1ae15b017d5e302b9541ec4b05aa374
[moonshot-ui.git] / src / moonshot-window.vala
1 using Gtk;
2
3 class MainWindow : Window
4 {
5     private const int WINDOW_WIDTH = 400;
6     private const int WINDOW_HEIGHT = 500;
7
8     private UIManager ui_manager = new UIManager();
9     private Entry search_entry;
10     private VBox vbox_right;
11     private CustomVBox custom_vbox;
12     private VBox services_internal_vbox;
13
14     private Entry username_entry;
15     private Entry password_entry;
16
17     private ListStore listmodel;
18     private TreeModelFilter filter;
19
20     public IdentitiesManager identities_manager;
21     private SList<IdCard>    candidates;
22
23     private MoonshotServer ipc_server;
24
25     private IdCard default_id_card;
26     public Queue<IdentityRequest> request_queue;
27
28     private HashTable<Gtk.Button, string> service_button_map;
29
30     private enum Columns
31     {
32         IDCARD_COL,
33         LOGO_COL,
34         ISSUER_COL,
35         USERNAME_COL,
36         PASSWORD_COL,
37         N_COLUMNS
38     }
39
40     private const string layout =
41 "
42 <menubar name='MenuBar'>
43         <menu name='FileMenu' action='FileMenuAction'>
44             <menuitem name='AddIdCard' action='AddIdCardAction' />
45             <separator />
46             <menuitem name='Quit' action='QuitAction' />
47         </menu>
48
49         <menu name='HelpMenu' action='HelpMenuAction'>
50              <menuitem name='About' action='AboutAction' />
51         </menu>
52 </menubar>
53 ";
54
55     public MainWindow()
56     {
57         request_queue = new Queue<IdentityRequest>();
58         
59         service_button_map = new HashTable<Gtk.Button, string> (direct_hash, direct_equal);
60
61         this.title = "Moonshoot";
62         this.set_position (WindowPosition.CENTER);
63         set_default_size (WINDOW_WIDTH, WINDOW_HEIGHT);
64
65         build_ui();
66         setup_identities_list();
67         load_id_cards();
68         connect_signals();
69         init_ipc_server();
70     }
71     
72     public void add_candidate (IdCard idcard)
73     {
74         candidates.append (idcard);
75     }
76
77     private bool visible_func (TreeModel model, TreeIter iter)
78     {
79         IdCard id_card;
80
81         model.get (iter,
82                    Columns.IDCARD_COL, out id_card);
83
84         if (id_card == null)
85             return false;
86         
87         if (candidates != null)
88         {
89             bool is_candidate = false;
90             foreach (IdCard candidate in candidates)
91             {
92                 if (candidate == id_card)
93                     is_candidate = true;
94             }
95             if (!is_candidate)
96                 return false;
97         }
98         
99         string entry_text = search_entry.get_text ();
100         if (entry_text == null || entry_text == "")
101         {
102             return true;
103         }
104
105         foreach (string search_text in entry_text.split(" "))
106         {
107             if (search_text == "")
108                 continue;
109          
110
111             string search_text_casefold = search_text.casefold ();
112
113             if (id_card.issuer != null)
114             {
115               string issuer_casefold = id_card.issuer;
116
117               if (issuer_casefold.contains (search_text_casefold))
118                   return true;
119             }
120
121             if (id_card.display_name != null)
122             {
123                 string display_name_casefold = id_card.display_name.casefold ();
124               
125                 if (display_name_casefold.contains (search_text_casefold))
126                     return true;
127             }
128             
129             if (id_card.services.length > 0)
130             {
131                 foreach (string service in id_card.services)
132                 {
133                     string service_casefold = service.casefold ();
134
135                     if (service_casefold.contains (search_text_casefold))
136                         return true;
137                 }
138             }
139         }
140         return false;
141     }
142
143     private void setup_identities_list ()
144     {
145        this.listmodel = new ListStore (Columns.N_COLUMNS, typeof (IdCard),
146                                                           typeof (Gdk.Pixbuf),
147                                                           typeof (string),
148                                                           typeof (string),
149                                                           typeof (string));
150       this.filter = new TreeModelFilter (listmodel, null);
151
152       filter.set_visible_func (visible_func);
153     }
154
155     private void search_entry_icon_press_cb (EntryIconPosition pos, Gdk.Event event)
156     {
157         if (pos == EntryIconPosition.PRIMARY)
158         {
159             print ("Search entry icon pressed\n");
160         }
161         else
162         {
163             this.search_entry.set_text ("");
164         }
165     }
166
167     private void search_entry_text_changed_cb ()
168     {
169         this.filter.refilter ();
170         redraw_id_card_widgets ();
171
172         var has_text = this.search_entry.get_text_length () > 0;
173         this.search_entry.set_icon_sensitive (EntryIconPosition.PRIMARY, has_text);
174         this.search_entry.set_icon_sensitive (EntryIconPosition.SECONDARY, has_text);
175
176         this.vbox_right.set_visible (false);
177     }
178
179     private bool search_entry_key_press_event_cb (Gdk.EventKey e)
180     {
181         if(Gdk.keyval_name(e.keyval) == "Escape")
182            this.search_entry.set_text("");
183
184         // Continue processing this event, since the
185         // text entry functionality needs to see it too.
186         return false;
187     }
188
189     private void load_id_cards ()
190     {
191         identities_manager = new IdentitiesManager ();
192         
193         if (identities_manager.id_card_list == null)
194           return;
195
196         foreach (IdCard id_card in identities_manager.id_card_list)
197         {
198             add_id_card_data (id_card);
199             add_id_card_widget (id_card);
200         }
201
202         this.default_id_card = identities_manager.id_card_list.data;
203     }
204
205     private void fill_details (IdCardWidget id_card_widget)
206     {
207        var id_card = id_card_widget.id_card;
208        this.username_entry.set_text (id_card.username);
209        this.password_entry.set_text (id_card.password ?? "");
210
211        var children = this.services_internal_vbox.get_children ();
212        foreach (var hbox in children)
213            hbox.destroy();
214        fill_services_vbox (id_card_widget.id_card);
215        identities_manager.store_id_cards();
216     }
217
218     private void show_details (IdCard id_card)
219     {
220        this.vbox_right.set_visible (!vbox_right.get_visible ());
221
222        if (this.vbox_right.get_visible () == false)
223        {
224            this.resize (WINDOW_WIDTH, WINDOW_HEIGHT);
225        }
226     }
227
228     private void details_identity_cb (IdCardWidget id_card_widget)
229     {
230        fill_details (id_card_widget);
231        show_details (id_card_widget.id_card);
232     }
233
234     private IdCard get_id_card_data (AddIdentityDialog dialog)
235     {
236         var id_card = new IdCard ();
237
238         id_card.display_name = dialog.display_name;
239         id_card.issuer = dialog.issuer;
240         if (id_card.issuer == "")
241             id_card.issuer = "Issuer";
242         id_card.username = dialog.username;
243         id_card.password = dialog.password;
244         id_card.services = {};
245         id_card.set_data("pixbuf", find_icon ("avatar-default", 48));
246
247         return id_card;
248     }
249
250     private void add_id_card_data (IdCard id_card)
251     {
252         TreeIter   iter;
253         Gdk.Pixbuf pixbuf;
254
255         this.listmodel.append (out iter);
256         pixbuf = id_card.get_data("pixbuf");
257         listmodel.set (iter,
258                        Columns.IDCARD_COL, id_card,
259                        Columns.LOGO_COL, pixbuf,
260                        Columns.ISSUER_COL, id_card.issuer,
261                        Columns.USERNAME_COL, id_card.username,
262                        Columns.PASSWORD_COL, id_card.password);
263     }
264
265     private void remove_id_card_data (IdCard id_card)
266     {
267         TreeIter iter;
268         string issuer;
269
270         if (listmodel.get_iter_first (out iter))
271         {
272             do
273             {
274                 listmodel.get (iter,
275                                Columns.ISSUER_COL, out issuer);
276
277                 if (id_card.issuer == issuer)
278                 {
279                     listmodel.remove (iter);
280                     break;
281                 }
282             }
283             while (listmodel.iter_next (ref iter));
284         }
285     }
286
287     private void add_id_card_widget (IdCard id_card)
288     {
289         var id_card_widget = new IdCardWidget (id_card);
290
291         this.custom_vbox.add_id_card_widget (id_card_widget);
292
293         id_card_widget.details_id.connect (details_identity_cb);
294         id_card_widget.remove_id.connect (remove_identity_cb);
295         id_card_widget.send_id.connect ((w) => send_identity_cb (w.id_card));
296         id_card_widget.expanded.connect (this.custom_vbox.receive_expanded_event);
297         id_card_widget.expanded.connect (fill_details);
298     }
299
300     /* This method finds a valid display name */
301     public bool display_name_is_valid (string name,
302                                        out string? candidate)
303     {
304         foreach (IdCard id_card in identities_manager.id_card_list)
305         {
306           if (id_card.display_name == name)
307           {
308             if (&candidate != null)
309             {
310               for (int i=0; i<1000; i++)
311               {
312                 string tmp = "%s %d".printf (name, i);
313                 if (display_name_is_valid (tmp, null))
314                 {
315                   candidate = tmp;
316                   break;
317                 }
318               }
319             }
320             return false;
321           }
322         }
323         
324         return true;
325     }
326     
327     public void insert_id_card (IdCard id_card)
328     {
329         string candidate;
330         
331         if (!display_name_is_valid (id_card.display_name, out candidate))
332         {
333           id_card.display_name = candidate;
334         }
335     
336         this.identities_manager.id_card_list.prepend (id_card);
337         this.identities_manager.store_id_cards ();
338
339         add_id_card_data (id_card);
340         add_id_card_widget (id_card);
341     }
342
343     public bool add_identity (IdCard id_card)
344     {
345         /* TODO: Check if display name already exists */
346
347         var dialog = new Gtk.MessageDialog (this,
348                                             Gtk.DialogFlags.DESTROY_WITH_PARENT,
349                                             Gtk.MessageType.QUESTION,
350                                             Gtk.ButtonsType.YES_NO,
351                                             _("Would you like to add '%s' ID Card to the ID Card Organizer?"),
352                                             id_card.display_name);
353
354         dialog.show_all ();
355         var ret = dialog.run ();
356         dialog.hide ();
357
358         if (ret == Gtk.ResponseType.YES) {
359             id_card.set_data ("pixbuf", find_icon ("avatar-default", 48));
360             this.insert_id_card (id_card);
361             return true;
362         }
363
364         return false;
365     }
366
367     private void add_identity_manual_cb ()
368     {
369         var dialog = new AddIdentityDialog ();
370         var result = dialog.run ();
371
372         switch (result) {
373         case ResponseType.OK:
374             insert_id_card (get_id_card_data (dialog));
375             break;
376         default:
377             break;
378         }
379         dialog.destroy ();
380     }
381
382     private void remove_id_card_widget (IdCardWidget id_card_widget)
383     {
384         remove_id_card_data (id_card_widget.id_card);
385
386         this.custom_vbox.remove_id_card_widget (id_card_widget);
387     }
388
389     private void remove_identity (IdCardWidget id_card_widget)
390     {
391         var id_card = id_card_widget.id_card;
392
393         this.identities_manager.id_card_list.remove (id_card);
394         this.identities_manager.store_id_cards ();
395
396         remove_id_card_widget (id_card_widget);
397     }
398
399     private void redraw_id_card_widgets ()
400     {
401         TreeIter iter;
402         IdCard id_card;
403
404         var children = this.custom_vbox.get_children ();
405         foreach (var id_card_widget in children)
406             id_card_widget.destroy();
407
408         if (filter.get_iter_first (out iter))
409         {
410             do
411             {
412                 filter.get (iter,
413                             Columns.IDCARD_COL, out id_card);
414
415                 add_id_card_widget (id_card);
416             }
417             while (filter.iter_next (ref iter));
418         }
419     }
420
421     private void remove_identity_cb (IdCardWidget id_card_widget)
422     {
423         var id_card = id_card_widget.id_card;
424
425         var dialog = new MessageDialog (null,
426                                         DialogFlags.DESTROY_WITH_PARENT,
427                                         MessageType.INFO,
428                                         Gtk.ButtonsType.YES_NO,
429                                         _("Are you sure you want to delete %s ID Card?"), id_card.issuer);
430         var result = dialog.run ();
431         switch (result) {
432         case ResponseType.YES:
433             remove_identity (id_card_widget);
434             break;
435         default:
436             break;
437         }
438         dialog.destroy ();
439     }
440
441     public void select_identity (IdentityRequest request)
442     {
443         IdCard identity = null;
444
445         this.request_queue.push_tail (request);
446         
447         if (custom_vbox.current_idcard != null &&
448             custom_vbox.current_idcard.send_button != null)
449           custom_vbox.current_idcard.send_button.set_sensitive (true);
450
451         if (request.select_default)
452         {
453             identity = default_id_card;
454         }
455
456         if (identity == null)
457         {
458             bool has_nai = request.nai != null && request.nai != "";
459             bool has_srv = request.service != null && request.service != "";
460             bool confirm = false;
461             IdCard nai_provided = null;
462
463             foreach (IdCard id in identities_manager.id_card_list)
464             {
465                 /* If NAI matches we add id card to the candidate list */
466                 if (has_nai && request.nai == id.nai)
467                 {
468                     nai_provided = id;
469                     add_candidate (id);
470                     continue;
471                 }
472
473                 /* If any service matches we add id card to the candidate list */
474                 if (has_srv)
475                 {
476                     foreach (string srv in id.services)
477                     {
478                         if (request.service == srv)
479                         {
480                             add_candidate (id);
481                             continue;
482                         }
483                     }
484                 }
485             }
486
487             /* If more than one candidate we dissasociate service from all ids */
488             if (has_srv && candidates.length() > 1)
489             {
490                 foreach (IdCard id in candidates)
491                 {
492                     int i = 0;
493                     SList<string> services_list = null;
494                     bool has_service = false;
495
496                     foreach (string srv in id.services)
497                     {
498                         if (srv == request.service)
499                         {
500                             has_service = true;
501                             continue;
502                         }
503                         services_list.append (srv);
504                     }
505                     
506                     if (!has_service)
507                         continue;
508
509                     if (services_list.length () == 0)
510                     {
511                         id.services = {};
512                         continue;
513                     }
514
515                     string[] services = new string[services_list.length ()];
516                     foreach (string srv in services_list)
517                     {
518                         services[i] = srv;
519                         i++;
520                     }
521
522                     id.services = services;
523                 }
524             }
525
526             identities_manager.store_id_cards ();
527
528             /* If there are no candidates we use the service matching rules */
529             if (candidates.length () == 0)
530             {
531                 foreach (IdCard id in identities_manager.id_card_list)
532                 {
533                     foreach (Rule rule in id.rules)
534                     {
535                         if (!match_service_pattern (request.service, rule.pattern))
536                             continue;
537
538                         candidates.append (id);
539
540                         if (rule.always_confirm == "true")
541                             confirm = true;
542                     }
543                 }
544             }
545             
546             if (candidates.length () > 1)
547             {
548                 if (has_nai && nai_provided != null)
549                 {
550                     identity = nai_provided;
551                     confirm = false;
552                 }
553                 else
554                     confirm = true;
555             }
556             else
557                 identity = candidates.nth_data (0);
558
559             /* TODO: If candidate list empty return fail */
560             
561             if (confirm)
562             {
563                 filter.refilter();
564                 redraw_id_card_widgets ();
565                 show ();
566                 return;
567             }
568         }
569         // Send back the identity (we can't directly run the
570         // callback because we may be being called from a 'yield')
571         Idle.add (() => { send_identity_cb (identity); return false; });
572         return;
573     }
574     
575     private bool match_service_pattern (string service, string pattern)
576     {
577         var pspec = new PatternSpec (pattern);
578         return pspec.match_string (service);
579     }
580
581     public void send_identity_cb (IdCard identity)
582     {
583         return_if_fail (request_queue.length > 0);
584
585         var request = this.request_queue.pop_head ();
586         bool reset_password = false;
587
588         if (request.service != null && request.service != "")
589         {
590             string[] services = new string[identity.services.length + 1];
591
592             for (int i = 0; i < identity.services.length; i++)
593                 services[i] = identity.services[i];
594
595             services[identity.services.length] = request.service;
596
597             identity.services = services;
598
599             identities_manager.store_id_cards();
600         }
601
602         if (identity.password == null)
603         {
604             var dialog = new AddPasswordDialog ();
605             var result = dialog.run ();
606
607             switch (result) {
608             case ResponseType.OK:
609                 identity.password = dialog.password;
610                 reset_password = ! dialog.remember;
611                 break;
612             default:
613                 identity = null;
614                 break;
615             }
616
617             dialog.destroy ();
618         }
619
620         if (this.request_queue.is_empty())
621             this.hide ();
622
623         if (identity != null)
624             this.default_id_card = identity;
625
626         request.return_identity (identity);
627
628         if (reset_password)
629             identity.password = null;
630
631         candidates = null;
632     }
633
634     private void label_make_bold (Label label)
635     {
636         var font_desc = new Pango.FontDescription ();
637
638         font_desc.set_weight (Pango.Weight.BOLD);
639
640         /* This will only affect the weight of the font, the rest is
641          * from the current state of the widget, which comes from the
642          * theme or user prefs, since the font desc only has the
643          * weight flag turned on.
644          */
645         label.modify_font (font_desc);
646     }
647
648     private void fill_services_vbox (IdCard id_card)
649     {
650         int i = 0;
651         var n_columns = id_card.services.length;
652
653         var services_table = new Table (n_columns, 2, false);
654         services_table.set_col_spacings (10);
655         services_table.set_row_spacings (10);
656         this.services_internal_vbox.add (services_table);
657         
658         service_button_map.remove_all ();
659
660         foreach (string service in id_card.services)
661         {
662             var label = new Label (service);
663             label.set_alignment (0, (float) 0.5);
664 #if VALA_0_12
665             var remove_button = new Button.from_stock (Stock.REMOVE);
666 #else
667             var remove_button = new Button.from_stock (STOCK_REMOVE);
668 #endif
669
670
671             service_button_map.insert (remove_button, service);
672             
673             remove_button.clicked.connect ((remove_button) =>
674             {
675               var dialog = new Gtk.MessageDialog (this,
676                                       Gtk.DialogFlags.DESTROY_WITH_PARENT,
677                                       Gtk.MessageType.QUESTION,
678                                       Gtk.ButtonsType.YES_NO,
679                                       _("Are you sure you want to stop '%s' ID Card to use %s?"),
680                                       custom_vbox.current_idcard.id_card.display_name);
681               var ret = dialog.run();
682               dialog.hide();
683               
684               if (ret == Gtk.ResponseType.YES)
685               {
686                 IdCard idcard = custom_vbox.current_idcard.id_card;
687                 var candidate = service_button_map.lookup (remove_button);
688
689                 SList<string> services = new SList<string>();
690                 
691                 foreach (string srv in idcard.services)
692                 {
693                   if (srv == candidate)
694                     continue;
695                   services.append (srv);
696                 }
697                 
698                 idcard.services = new string[services.length()];
699                 for (int j=0; j<idcard.services.length; j++)
700                 {
701                   idcard.services[j] = services.nth_data(j);
702                 }
703                 
704                 var children = services_internal_vbox.get_children ();
705                 foreach (var hbox in children)
706                   hbox.destroy();
707                 
708                 fill_services_vbox (idcard);
709                 custom_vbox.current_idcard.update_id_card_label ();
710               }
711               
712             });
713             services_table.attach_defaults (label, 0, 1, i, i+1);
714             services_table.attach_defaults (remove_button, 1, 2, i, i+1);
715             i++;
716         }
717         this.services_internal_vbox.show_all ();
718     }
719
720     private void on_about_action ()
721     {
722         string[] authors = {
723             "Javier Jardón <jjardon@codethink.co.uk>",
724             "Sam Thursfield <samthursfield@codethink.co.uk>",
725             "Alberto Ruiz <alberto.ruiz@codethink.co.uk>",
726             null
727         };
728
729         string copyright = "Copyright 2011 JANET";
730
731         string license =
732 """
733 Copyright (c) 2011, JANET(UK)
734 All rights reserved.
735
736 Redistribution and use in source and binary forms, with or without
737 modification, are permitted provided that the following conditions
738 are met:
739
740 1. Redistributions of source code must retain the above copyright
741    notice, this list of conditions and the following disclaimer.
742
743 2. Redistributions in binary form must reproduce the above copyright
744    notice, this list of conditions and the following disclaimer in the
745    documentation and/or other materials provided with the distribution.
746
747 3. Neither the name of JANET(UK) nor the names of its contributors
748    may be used to endorse or promote products derived from this software
749    without specific prior written permission.
750
751 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"
752 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
753 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
754 ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
755 FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
756 DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
757 OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
758 HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
759 LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
760 OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
761 SUCH DAMAGE.
762 """;
763
764         Gtk.show_about_dialog (this,
765             "comments", _("Moonshot project UI"),
766             "copyright", copyright,
767             "website", "http://www.project-moonshot.org/",
768             "license", license,
769             "website-label", _("Visit the Moonshot project web site"),
770             "authors", authors,
771             "translator-credits", _("translator-credits"),
772             null
773         );
774     }
775
776     private Gtk.ActionEntry[] create_actions() {
777         Gtk.ActionEntry[] actions = new Gtk.ActionEntry[0];
778
779         Gtk.ActionEntry filemenu = { "FileMenuAction",
780                                      null,
781                                      N_("_File"),
782                                      null, null, null };
783         actions += filemenu;
784         Gtk.ActionEntry add = { "AddIdCardAction",
785 #if VALA_0_12
786                                 Stock.ADD,
787 #else
788                                 STOCK_ADD,
789 #endif
790                                 N_("Add ID Card"),
791                                 null,
792                                 N_("Add a new ID Card"),
793                                 add_identity_manual_cb };
794         actions += add;
795         Gtk.ActionEntry quit = { "QuitAction",
796 #if VALA_0_12
797                                  Stock.QUIT,
798 #else
799                                  STOCK_QUIT,
800 #endif
801                                  N_("Quit"),
802                                  "<control>Q",
803                                  N_("Quit the application"),
804                                  Gtk.main_quit };
805         actions += quit;
806
807         Gtk.ActionEntry helpmenu = { "HelpMenuAction",
808                                      null,
809                                      N_("_Help"),
810                                      null, null, null };
811         actions += helpmenu;
812         Gtk.ActionEntry about = { "AboutAction",
813 #if VALA_0_12
814                                   Stock.ABOUT,
815 #else
816                                   STOCK_ABOUT,
817 #endif
818                                   N_("About"),
819                                   null,
820                                   N_("About this application"),
821                                   on_about_action };
822         actions += about;
823
824         return actions;
825     }
826
827
828     private void create_ui_manager ()
829     {
830         Gtk.ActionGroup action_group = new Gtk.ActionGroup ("GeneralActionGroup");
831         action_group.add_actions (create_actions (), this);
832         ui_manager.insert_action_group (action_group, 0);
833         try
834         {
835             ui_manager.add_ui_from_string (layout, -1);
836         }
837         catch (Error e)
838         {
839             stderr.printf ("%s\n", e.message);
840         }
841         ui_manager.ensure_update ();
842     }
843
844     private void build_ui()
845     {
846         create_ui_manager ();
847
848         this.search_entry = new Entry();
849
850         set_atk_name_description (search_entry, _("Search entry"), _("Search for a specific ID Card"));
851         this.search_entry.set_icon_from_pixbuf (EntryIconPosition.PRIMARY,
852                                                 find_icon_sized ("edit-find-symbolic", Gtk.IconSize.MENU));
853         this.search_entry.set_icon_tooltip_text (EntryIconPosition.PRIMARY,
854                                                  _("Search identity or service"));
855         this.search_entry.set_icon_sensitive (EntryIconPosition.PRIMARY, false);
856
857         this.search_entry.set_icon_from_pixbuf (EntryIconPosition.SECONDARY,
858                                                 find_icon_sized ("edit-clear-symbolic", Gtk.IconSize.MENU));
859         this.search_entry.set_icon_tooltip_text (EntryIconPosition.SECONDARY,
860                                                  _("Clear the current search"));
861         this.search_entry.set_icon_sensitive (EntryIconPosition.SECONDARY, false);
862
863
864         this.search_entry.icon_press.connect (search_entry_icon_press_cb);
865         this.search_entry.notify["text"].connect (search_entry_text_changed_cb);
866         this.search_entry.key_press_event.connect(search_entry_key_press_event_cb);
867
868         this.custom_vbox = new CustomVBox (this, false, 6);
869
870         var viewport = new Viewport (null, null);
871         viewport.set_border_width (6);
872         viewport.set_shadow_type (ShadowType.NONE);
873         viewport.add (custom_vbox);
874         var scroll = new ScrolledWindow (null, null);
875         scroll.set_policy (PolicyType.NEVER, PolicyType.AUTOMATIC);
876         scroll.set_shadow_type (ShadowType.IN);
877         scroll.add_with_viewport (viewport);
878
879         var vbox_left = new VBox (false, 0);
880         vbox_left.pack_start (search_entry, false, false, 6);
881         vbox_left.pack_start (scroll, true, true, 0);
882         vbox_left.set_size_request (WINDOW_WIDTH, 0);
883
884         var login_vbox_title = new Label (_("Login: "));
885         label_make_bold (login_vbox_title);
886         login_vbox_title.set_alignment (0, (float) 0.5);
887         var username_label = new Label (_("Username:"));
888         username_label.set_alignment (1, (float) 0.5);
889         this.username_entry = new Entry ();
890         var password_label = new Label (_("Password:"));
891         password_label.set_alignment (1, (float) 0.5);
892         this.password_entry = new Entry ();
893         password_entry.set_invisible_char ('*');
894         password_entry.set_visibility (false);
895         var remember_checkbutton = new CheckButton.with_label (_("Remember password"));
896         var login_table = new Table (3, 3, false);
897         login_table.set_col_spacings (10);
898         login_table.set_row_spacings (10);
899         login_table.attach_defaults (username_label, 0, 1, 0, 1);
900         login_table.attach_defaults (username_entry, 1, 2, 0, 1);
901         login_table.attach_defaults (password_label, 0, 1, 1, 2);
902         login_table.attach_defaults (password_entry, 1, 2, 1, 2);
903         login_table.attach_defaults (remember_checkbutton,  1, 2, 2, 3);
904         var login_vbox_alignment = new Alignment (0, 0, 0, 0);
905         login_vbox_alignment.set_padding (0, 0, 12, 0);
906         login_vbox_alignment.add (login_table);
907         var login_vbox = new VBox (false, 6);
908         login_vbox.pack_start (login_vbox_title, false, true, 0);
909         login_vbox.pack_start (login_vbox_alignment, false, true, 0);
910
911         var services_vbox_title = new Label (_("Services:"));
912         label_make_bold (services_vbox_title);
913         services_vbox_title.set_alignment (0, (float) 0.5);
914         var services_vbox_alignment = new Alignment (0, 0, 0, 0);
915         services_vbox_alignment.set_padding (0, 0, 12, 0);
916         this.services_internal_vbox = new VBox (true, 6);
917         services_vbox_alignment.add (services_internal_vbox);
918         var services_vbox = new VBox (false, 6);
919         services_vbox.pack_start (services_vbox_title, false, true, 0);
920         services_vbox.pack_start (services_vbox_alignment, false, true, 0);
921
922         this.vbox_right = new VBox (false, 18);
923         vbox_right.pack_start (login_vbox, false, true, 0);
924         vbox_right.pack_start (services_vbox, false, true, 0);
925
926         var hbox = new HBox (false, 12);
927         hbox.pack_start (vbox_left, true, true, 0);
928         hbox.pack_start (vbox_right, false, false, 0);
929
930         var main_vbox = new VBox (false, 0);
931         main_vbox.set_border_width (12);
932         var menubar = this.ui_manager.get_widget ("/MenuBar");
933         main_vbox.pack_start (menubar, false, false, 0);
934         main_vbox.pack_start (hbox, true, true, 0);
935         add (main_vbox);
936
937         main_vbox.show_all();
938         this.vbox_right.hide ();
939     }
940
941     private void set_atk_name_description (Widget widget, string name, string description)
942     {
943        var atk_widget = widget.get_accessible ();
944
945        atk_widget.set_name (name);
946        atk_widget.set_description (description);
947     }
948
949     private void connect_signals()
950     {
951         this.destroy.connect (Gtk.main_quit);
952     }
953
954     private void init_ipc_server ()
955     {
956 #if IPC_MSRPC
957         /* Errors will currently be sent via g_log - ie. to an
958          * obtrusive message box, on Windows
959          */
960         this.ipc_server = MoonshotServer.get_instance ();
961         MoonshotServer.start (this);
962 #else
963         try {
964             var conn = DBus.Bus.get (DBus.BusType.SESSION);
965             dynamic DBus.Object bus = conn.get_object ("org.freedesktop.DBus",
966                                                        "/org/freedesktop/DBus",
967                                                        "org.freedesktop.DBus");
968
969             // try to register service in session bus
970             uint reply = bus.request_name ("org.janet.Moonshot", (uint) 0);
971             assert (reply == DBus.RequestNameReply.PRIMARY_OWNER);
972
973             this.ipc_server = new MoonshotServer (this);
974             conn.register_object ("/org/janet/moonshot", ipc_server);
975
976         }
977         catch (DBus.Error e)
978         {
979             stderr.printf ("%s\n", e.message);
980         }
981 #endif
982     }
983
984     public static int main(string[] args)
985     {
986         Gtk.init(ref args);
987
988 #if OS_WIN32
989         // Force specific theme settings on Windows without requiring a gtkrc file
990         Gtk.Settings settings = Gtk.Settings.get_default ();
991         settings.set_string_property ("gtk-theme-name", "ms-windows", "moonshot");
992         settings.set_long_property ("gtk-menu-images", 0, "moonshot");
993 #endif
994
995         Intl.bindtextdomain (Config.GETTEXT_PACKAGE, Config.LOCALEDIR);
996         Intl.bind_textdomain_codeset (Config.GETTEXT_PACKAGE, "UTF-8");
997         Intl.textdomain (Config.GETTEXT_PACKAGE);
998
999         var window = new MainWindow();
1000         window.show ();
1001
1002         Gtk.main();
1003
1004         return 0;
1005     }
1006 }