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