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