3 class MainWindow : Window
5 private const int WINDOW_WIDTH = 400;
6 private const int WINDOW_HEIGHT = 500;
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;
14 private Entry username_entry;
15 private Entry password_entry;
17 private ListStore listmodel;
18 private TreeModelFilter filter;
20 public IdentitiesManager identities_manager;
22 private MoonshotServer ipc_server;
24 private IdCard default_id_card;
25 public Queue<IdentityRequest> request_queue;
27 private HashTable<Gtk.Button, string> service_button_map;
39 private const string layout =
41 <menubar name='MenuBar'>
42 <menu name='FileMenu' action='FileMenuAction'>
43 <menuitem name='AddIdCard' action='AddIdCardAction' />
45 <menuitem name='Quit' action='QuitAction' />
48 <menu name='HelpMenu' action='HelpMenuAction'>
49 <menuitem name='About' action='AboutAction' />
56 request_queue = new Queue<IdentityRequest>();
58 service_button_map = new HashTable<Gtk.Button, string> (direct_hash, direct_equal);
60 this.title = "Moonshoot";
61 this.set_position (WindowPosition.CENTER);
62 set_default_size (WINDOW_WIDTH, WINDOW_HEIGHT);
65 setup_identities_list();
71 private bool visible_func (TreeModel model, TreeIter iter)
75 string issuer_casefold;
76 string search_text_casefold;
79 Columns.ISSUER_COL, out issuer);
80 search_text = this.search_entry.get_text ();
82 if (issuer == null || search_text == null)
85 issuer_casefold = issuer.casefold ();
86 search_text_casefold = search_text.casefold ();
88 if (issuer_casefold.contains (search_text_casefold))
94 private void setup_identities_list ()
96 this.listmodel = new ListStore (Columns.N_COLUMNS, typeof (IdCard),
101 this.filter = new TreeModelFilter (listmodel, null);
103 filter.set_visible_func (visible_func);
106 private void search_entry_icon_press_cb (EntryIconPosition pos, Gdk.Event event)
108 if (pos == EntryIconPosition.PRIMARY)
110 print ("Search entry icon pressed\n");
114 this.search_entry.set_text ("");
118 private void search_entry_text_changed_cb ()
120 this.filter.refilter ();
121 redraw_id_card_widgets ();
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);
127 this.vbox_right.set_visible (false);
128 this.resize (WINDOW_WIDTH, WINDOW_HEIGHT);
131 private bool search_entry_key_press_event_cb (Gdk.EventKey e)
133 if(Gdk.keyval_name(e.keyval) == "Escape")
134 this.search_entry.set_text("");
136 // Continue processing this event, since the
137 // text entry functionality needs to see it too.
141 private void load_id_cards ()
143 identities_manager = new IdentitiesManager ();
145 if (identities_manager.id_card_list == null)
148 foreach (IdCard id_card in identities_manager.id_card_list)
150 add_id_card_data (id_card);
151 add_id_card_widget (id_card);
154 this.default_id_card = identities_manager.id_card_list.data;
157 private void fill_details (IdCardWidget id_card_widget)
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 ?? "");
163 var children = this.services_internal_vbox.get_children ();
164 foreach (var hbox in children)
166 fill_services_vbox (id_card_widget.id_card);
167 identities_manager.store_id_cards();
170 private void show_details (IdCard id_card)
172 this.vbox_right.set_visible (!vbox_right.get_visible ());
174 if (this.vbox_right.get_visible () == false)
176 this.resize (WINDOW_WIDTH, WINDOW_HEIGHT);
180 private void details_identity_cb (IdCardWidget id_card_widget)
182 fill_details (id_card_widget);
183 show_details (id_card_widget.id_card);
186 private IdCard get_id_card_data (AddIdentityDialog dialog)
188 var id_card = new IdCard ();
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 = {};
202 private void add_id_card_data (IdCard id_card)
206 this.listmodel.append (out 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);
215 private void remove_id_card_data (IdCard id_card)
220 if (listmodel.get_iter_first (out iter))
225 Columns.ISSUER_COL, out issuer);
227 if (id_card.issuer == issuer)
229 listmodel.remove (iter);
233 while (listmodel.iter_next (ref iter));
237 private void add_id_card_widget (IdCard id_card)
239 var id_card_widget = new IdCardWidget (id_card);
241 this.custom_vbox.add_id_card_widget (id_card_widget);
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);
250 private void add_identity (AddIdentityDialog dialog)
252 insert_id_card (get_id_card_data (dialog));
255 /* This method finds a valid display name */
256 public bool display_name_is_valid (string name,
257 out string? candidate)
259 foreach (IdCard id_card in identities_manager.id_card_list)
261 if (id_card.display_name == name)
263 if (&candidate != null)
265 for (int i=0; i<1000; i++)
267 string tmp = "%s %d".printf (name, i);
268 if (display_name_is_valid (tmp, null))
282 public void insert_id_card (IdCard id_card)
286 if (!display_name_is_valid (id_card.display_name, out candidate))
288 id_card.display_name = candidate;
291 this.identities_manager.id_card_list.prepend (id_card);
292 this.identities_manager.store_id_cards ();
294 add_id_card_data (id_card);
295 add_id_card_widget (id_card);
298 private void add_identity_cb ()
300 var dialog = new AddIdentityDialog ();
301 var result = dialog.run ();
304 case ResponseType.OK:
305 add_identity (dialog);
313 private void remove_id_card_widget (IdCardWidget id_card_widget)
315 remove_id_card_data (id_card_widget.id_card);
317 this.custom_vbox.remove_id_card_widget (id_card_widget);
320 private void remove_identity (IdCardWidget id_card_widget)
322 var id_card = id_card_widget.id_card;
324 this.identities_manager.id_card_list.remove (id_card);
325 this.identities_manager.store_id_cards ();
327 remove_id_card_widget (id_card_widget);
330 private void redraw_id_card_widgets ()
335 var children = this.custom_vbox.get_children ();
336 foreach (var id_card_widget in children)
337 id_card_widget.destroy();
339 if (filter.get_iter_first (out iter))
344 Columns.IDCARD_COL, out id_card);
346 add_id_card_widget (id_card);
348 while (filter.iter_next (ref iter));
352 private void remove_identity_cb (IdCardWidget id_card_widget)
354 var id_card = id_card_widget.id_card;
356 var dialog = new MessageDialog (null,
357 DialogFlags.DESTROY_WITH_PARENT,
359 Gtk.ButtonsType.YES_NO,
360 _("Are you sure you want to delete %s ID Card?"), id_card.issuer);
361 var result = dialog.run ();
363 case ResponseType.YES:
364 remove_identity (id_card_widget);
372 public void select_identity (IdentityRequest request)
374 IdCard identity = null;
376 this.request_queue.push_tail (request);
378 if (custom_vbox.current_idcard != null &&
379 custom_vbox.current_idcard.send_button != null)
380 custom_vbox.current_idcard.send_button.set_sensitive (true);
382 if (request.select_default)
384 identity = this.default_id_card;
387 /* Automatic service matching rules can go here */
389 if (identity == null)
391 // Resort to manual selection
396 // Send back the identity (we can't directly run the
397 // callback because we may be being called from a 'yield')
398 Idle.add (() => { send_identity_cb (identity); return false; });
403 public void send_identity_cb (IdCard identity)
405 return_if_fail (request_queue.length > 0);
407 var request = this.request_queue.pop_head ();
408 bool reset_password = false;
410 if (identity.password == null)
412 var dialog = new AddPasswordDialog ();
413 var result = dialog.run ();
416 case ResponseType.OK:
417 identity.password = dialog.password;
418 reset_password = ! dialog.remember;
428 if (this.request_queue.is_empty())
431 if (identity != null)
432 this.default_id_card = identity;
434 request.return_identity (identity);
437 identity.password = null;
440 private void label_make_bold (Label label)
442 var font_desc = new Pango.FontDescription ();
444 font_desc.set_weight (Pango.Weight.BOLD);
446 /* This will only affect the weight of the font, the rest is
447 * from the current state of the widget, which comes from the
448 * theme or user prefs, since the font desc only has the
449 * weight flag turned on.
451 label.modify_font (font_desc);
454 private void fill_services_vbox (IdCard id_card)
457 var n_columns = id_card.services.length;
459 var services_table = new Table (n_columns, 2, false);
460 services_table.set_col_spacings (10);
461 services_table.set_row_spacings (10);
462 this.services_internal_vbox.add (services_table);
464 service_button_map.remove_all ();
466 foreach (string service in id_card.services)
468 var label = new Label (service);
469 label.set_alignment (0, (float) 0.5);
471 var remove_button = new Button.from_stock (Stock.REMOVE);
473 var remove_button = new Button.from_stock (STOCK_REMOVE);
477 service_button_map.insert (remove_button, service);
479 remove_button.clicked.connect ((remove_button) =>
481 var dialog = new Gtk.MessageDialog (this,
482 Gtk.DialogFlags.DESTROY_WITH_PARENT,
483 Gtk.MessageType.QUESTION,
484 Gtk.ButtonsType.YES_NO,
485 _("Are you sure you want to stop '%s' ID Card to use %s?"),
486 custom_vbox.current_idcard.id_card.display_name);
487 var ret = dialog.run();
490 if (ret == Gtk.ResponseType.YES)
492 IdCard idcard = custom_vbox.current_idcard.id_card;
493 var candidate = service_button_map.lookup (remove_button);
495 SList<string> services = new SList<string>();
497 foreach (string srv in id_card.services)
499 if (srv == candidate)
501 services.append (srv);
504 id_card.services = new string[services.length()];
505 for (int j=0; j<id_card.services.length; j++)
507 id_card.services[j] = services.nth_data(j);
510 var children = services_internal_vbox.get_children ();
511 foreach (var hbox in children)
514 fill_services_vbox (id_card);
515 custom_vbox.current_idcard.update_id_card_label ();
519 services_table.attach_defaults (label, 0, 1, i, i+1);
520 services_table.attach_defaults (remove_button, 1, 2, i, i+1);
523 this.services_internal_vbox.show_all ();
526 private void on_about_action ()
529 "Javier Jardón <jjardon@codethink.co.uk>",
530 "Sam Thursfield <samthursfield@codethink.co.uk>",
531 "Alberto Ruiz <alberto.ruiz@codethink.co.uk>",
535 string copyright = "Copyright 2011 JANET";
539 Copyright (c) 2011, JANET(UK)
542 Redistribution and use in source and binary forms, with or without
543 modification, are permitted provided that the following conditions
546 1. Redistributions of source code must retain the above copyright
547 notice, this list of conditions and the following disclaimer.
549 2. Redistributions in binary form must reproduce the above copyright
550 notice, this list of conditions and the following disclaimer in the
551 documentation and/or other materials provided with the distribution.
553 3. Neither the name of JANET(UK) nor the names of its contributors
554 may be used to endorse or promote products derived from this software
555 without specific prior written permission.
557 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"
558 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
559 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
560 ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
561 FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
562 DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
563 OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
564 HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
565 LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
566 OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
570 Gtk.show_about_dialog (this,
571 "comments", _("Moonshot project UI"),
572 "copyright", copyright,
573 "website", "http://www.project-moonshot.org/",
575 "website-label", _("Visit the Moonshot project web site"),
577 "translator-credits", _("translator-credits"),
582 private Gtk.ActionEntry[] create_actions() {
583 Gtk.ActionEntry[] actions = new Gtk.ActionEntry[0];
585 Gtk.ActionEntry filemenu = { "FileMenuAction",
590 Gtk.ActionEntry add = { "AddIdCardAction",
598 N_("Add a new ID Card"),
601 Gtk.ActionEntry quit = { "QuitAction",
609 N_("Quit the application"),
613 Gtk.ActionEntry helpmenu = { "HelpMenuAction",
618 Gtk.ActionEntry about = { "AboutAction",
626 N_("About this application"),
634 private void create_ui_manager ()
636 Gtk.ActionGroup action_group = new Gtk.ActionGroup ("GeneralActionGroup");
637 action_group.add_actions (create_actions (), this);
638 ui_manager.insert_action_group (action_group, 0);
641 ui_manager.add_ui_from_string (layout, -1);
645 stderr.printf ("%s\n", e.message);
647 ui_manager.ensure_update ();
650 private void build_ui()
652 create_ui_manager ();
654 this.search_entry = new Entry();
656 set_atk_name_description (search_entry, _("Search entry"), _("Search for a specific ID Card"));
657 this.search_entry.set_icon_from_pixbuf (EntryIconPosition.PRIMARY,
658 find_icon_sized ("edit-find-symbolic", Gtk.IconSize.MENU));
659 this.search_entry.set_icon_tooltip_text (EntryIconPosition.PRIMARY,
660 _("Search identity or service"));
661 this.search_entry.set_icon_sensitive (EntryIconPosition.PRIMARY, false);
663 this.search_entry.set_icon_from_pixbuf (EntryIconPosition.SECONDARY,
664 find_icon_sized ("edit-clear-symbolic", Gtk.IconSize.MENU));
665 this.search_entry.set_icon_tooltip_text (EntryIconPosition.SECONDARY,
666 _("Clear the current search"));
667 this.search_entry.set_icon_sensitive (EntryIconPosition.SECONDARY, false);
670 this.search_entry.icon_press.connect (search_entry_icon_press_cb);
671 this.search_entry.notify["text"].connect (search_entry_text_changed_cb);
672 this.search_entry.key_press_event.connect(search_entry_key_press_event_cb);
674 this.custom_vbox = new CustomVBox (this, false, 6);
676 var viewport = new Viewport (null, null);
677 viewport.set_border_width (6);
678 viewport.set_shadow_type (ShadowType.NONE);
679 viewport.add (custom_vbox);
680 var scroll = new ScrolledWindow (null, null);
681 scroll.set_policy (PolicyType.NEVER, PolicyType.AUTOMATIC);
682 scroll.set_shadow_type (ShadowType.IN);
683 scroll.add_with_viewport (viewport);
685 var vbox_left = new VBox (false, 0);
686 vbox_left.pack_start (search_entry, false, false, 6);
687 vbox_left.pack_start (scroll, true, true, 0);
688 vbox_left.set_size_request (WINDOW_WIDTH, 0);
690 var login_vbox_title = new Label (_("Login: "));
691 label_make_bold (login_vbox_title);
692 login_vbox_title.set_alignment (0, (float) 0.5);
693 var username_label = new Label (_("Username:"));
694 username_label.set_alignment (1, (float) 0.5);
695 this.username_entry = new Entry ();
696 var password_label = new Label (_("Password:"));
697 password_label.set_alignment (1, (float) 0.5);
698 this.password_entry = new Entry ();
699 password_entry.set_invisible_char ('*');
700 password_entry.set_visibility (false);
701 var remember_checkbutton = new CheckButton.with_label (_("Remember password"));
702 var login_table = new Table (3, 3, false);
703 login_table.set_col_spacings (10);
704 login_table.set_row_spacings (10);
705 login_table.attach_defaults (username_label, 0, 1, 0, 1);
706 login_table.attach_defaults (username_entry, 1, 2, 0, 1);
707 login_table.attach_defaults (password_label, 0, 1, 1, 2);
708 login_table.attach_defaults (password_entry, 1, 2, 1, 2);
709 login_table.attach_defaults (remember_checkbutton, 1, 2, 2, 3);
710 var login_vbox_alignment = new Alignment (0, 0, 0, 0);
711 login_vbox_alignment.set_padding (0, 0, 12, 0);
712 login_vbox_alignment.add (login_table);
713 var login_vbox = new VBox (false, 6);
714 login_vbox.pack_start (login_vbox_title, false, true, 0);
715 login_vbox.pack_start (login_vbox_alignment, false, true, 0);
717 var services_vbox_title = new Label (_("Services:"));
718 label_make_bold (services_vbox_title);
719 services_vbox_title.set_alignment (0, (float) 0.5);
720 var services_vbox_alignment = new Alignment (0, 0, 0, 0);
721 services_vbox_alignment.set_padding (0, 0, 12, 0);
722 this.services_internal_vbox = new VBox (true, 6);
723 services_vbox_alignment.add (services_internal_vbox);
724 var services_vbox = new VBox (false, 6);
725 services_vbox.pack_start (services_vbox_title, false, true, 0);
726 services_vbox.pack_start (services_vbox_alignment, false, true, 0);
728 this.vbox_right = new VBox (false, 18);
729 vbox_right.pack_start (login_vbox, false, true, 0);
730 vbox_right.pack_start (services_vbox, false, true, 0);
732 var hbox = new HBox (false, 12);
733 hbox.pack_start (vbox_left, true, true, 0);
734 hbox.pack_start (vbox_right, false, false, 0);
736 var main_vbox = new VBox (false, 0);
737 main_vbox.set_border_width (12);
738 var menubar = this.ui_manager.get_widget ("/MenuBar");
739 main_vbox.pack_start (menubar, false, false, 0);
740 main_vbox.pack_start (hbox, true, true, 0);
743 main_vbox.show_all();
744 this.vbox_right.hide ();
747 private void set_atk_name_description (Widget widget, string name, string description)
749 var atk_widget = widget.get_accessible ();
751 atk_widget.set_name (name);
752 atk_widget.set_description (description);
755 private void connect_signals()
757 this.destroy.connect (Gtk.main_quit);
760 private void init_ipc_server ()
763 /* Errors will currently be sent via g_log - ie. to an
764 * obtrusive message box, on Windows
766 this.ipc_server = MoonshotServer.get_instance ();
767 MoonshotServer.start (this);
770 var conn = DBus.Bus.get (DBus.BusType.SESSION);
771 dynamic DBus.Object bus = conn.get_object ("org.freedesktop.DBus",
772 "/org/freedesktop/DBus",
773 "org.freedesktop.DBus");
775 // try to register service in session bus
776 uint reply = bus.request_name ("org.janet.Moonshot", (uint) 0);
777 assert (reply == DBus.RequestNameReply.PRIMARY_OWNER);
779 this.ipc_server = new MoonshotServer (this);
780 conn.register_object ("/org/janet/moonshot", ipc_server);
785 stderr.printf ("%s\n", e.message);
790 public static int main(string[] args)
795 // Force specific theme settings on Windows without requiring a gtkrc file
796 Gtk.Settings settings = Gtk.Settings.get_default ();
797 settings.set_string_property ("gtk-theme-name", "ms-windows", "moonshot");
798 settings.set_long_property ("gtk-menu-images", 0, "moonshot");
801 Intl.bindtextdomain (Config.GETTEXT_PACKAGE, Config.LOCALEDIR);
802 Intl.bind_textdomain_codeset (Config.GETTEXT_PACKAGE, "UTF-8");
803 Intl.textdomain (Config.GETTEXT_PACKAGE);
805 var window = new MainWindow();