Update homepage URL and author email address
[email-reminder.git] / email-reminder-editor
blobfa50dccdd9f846f5d8b06a1b4ff569d29dc91f5e
1 #!/usr/bin/perl
3 =head1 NAME
5 Email-Reminder-Editor - edit special occasion reminders
7 =head1 SYNOPSIS
9 Simple editor for modifying special occasion email reminders.
11 =head1 DESCRIPTION
13 Email-reminder allows users to define events that they want to be
14 reminded of by email. Possible events include birthdays,
15 anniversaries and yearly events. Reminders can be sent on the day of
16 the event and a few days beforehand.
18 This is a simple editor that allows users to add/modify their
19 reminders. It saves changes automatically when the program is closed.
21 =head1 OPTIONS
23 =over 6
25 =item B<--help>
27 Displays basic usage message.
29 =item B<--simulate>
31 Does not actually save any changes.
33 =item B<--verbose>
35 Prints out information about what the program is doing.
37 =item B<--version>
39 Displays the version number.
41 =back
43 =head1 FILES
45 F<~/.email-reminders>
47 =head1 AUTHOR
49 Francois Marier <francois@fmarier.org>
51 =head1 SEE ALSO
53 collect-reminders, send-reminders
55 =head1 COPYRIGHT
57 Copyright (C) 2004-2010 by Francois Marier
59 Email-Reminder is free software; you can redistribute it and/or
60 modify it under the terms of the GNU General Public License as
61 published by the Free Software Foundation; either version 3 of the
62 License, or (at your option) any later version.
64 Email-Reminder is distributed in the hope that it will be useful,
65 but WITHOUT ANY WARRANTY; without even the implied warranty of
66 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
67 General Public License for more details.
69 You should have received a copy of the GNU General Public License
70 along with Email-Reminder; if not, write to the Free Software
71 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
72 02110-1301, USA.
74 =cut
76 use strict;
77 use warnings;
79 use Getopt::Long;
80 use Pod::Usage;
82 use Gtk2 '-init';
83 use Gtk2::SimpleList;
84 use Gtk2::SimpleMenu;
86 use constant TRUE => 1;
87 use constant FALSE => 0;
89 use EmailReminder::Event;
90 use EmailReminder::EventList;
92 # Command-line parameters
93 my $verbose = 0;
94 my $debug = 0;
95 my $simulate = 0;
96 my $version = 0;
97 my $help = 0;
98 GetOptions("verbose" => \$verbose,
99 "debug" => \$debug,
100 "simulate" => \$simulate,
101 "version" => \$version,
102 "help" => \$help,
105 # Constants
106 my $ANNIVERSARY_TAB = 'Anniversaries';
107 my $BIRTHDAY_TAB = 'Birthdays';
108 my $MONTHLY_TAB = 'Monthly Events';
109 my $WEEKLY_TAB = 'Weekly Events';
110 my $YEARLY_TAB = 'Yearly Events';
112 my $EMAIL_PREF_LABEL = 'Email:';
113 my $NAME_PREF_LABEL = 'Name:';
115 my $NEW_EVENT_NAME = '<new event>';
116 my $WINDOW_TITLE = 'Email-Reminder Editor';
118 my $DEFAULT_WIDTH = 600;
119 my $DEFAULT_HEIGHT = 400;
121 my $SEND_REMINDERS_EXECUTABLE = '/usr/bin/send-reminders';
123 # Global variables
124 my $window;
125 my $notebook;
126 my $events;
128 sub load_data
130 $events = EmailReminder::EventList->new(glob("~/" . $EmailReminder::Utils::USER_CONFIG_FILE), TRUE);
131 print "Events loaded.\n" if $verbose;
132 return;
135 sub save_data
137 $events->save($verbose) unless $simulate;
138 print "Events saved.\n" if $verbose;
139 return;
142 sub text_cell_edited
144 my ($cell_renderer, $text_path, $new_text, $model) = @_;
145 my $path = Gtk2::TreePath->new_from_string($text_path);
146 $model->set_value($path, $cell_renderer->{column}, $new_text);
147 return 1;
150 sub sort_by_column
152 return 0;
155 sub create_listbox
157 my $type = shift;
158 my @columns = ('ID', @_);
160 # TODO: make listbox sortable by clicking on column headers
161 my $model = $events->get_model($type);
162 #my $sort_model = Gtk2::TreeModelSort->new_with_model($model);
163 #$sort_model->set_default_sort_func(\&_sort_by_column);
164 #$sort_model->set_sort_column_id(1, 'ascending');
165 my $list = Gtk2::TreeView->new($model);
166 $list->{type} = $type;
168 my $col_num = 0;
169 foreach my $title (@columns) {
170 my $renderer = Gtk2::CellRendererText->new();
171 $renderer->set(editable => TRUE);
172 $renderer->{column} = $col_num;
173 $renderer->signal_connect (edited => \&text_cell_edited, $model);
175 my $col = Gtk2::TreeViewColumn->new_with_attributes($title, $renderer, 'text' => $col_num);
176 $col->set_resizable(TRUE);
177 #$col->set_clickable(FALSE);
178 #$col->set_sort_column_id($col_num);
179 $list->append_column($col);
181 $col_num++;
184 $list->get_column(0)->set_visible(FALSE) unless $debug; # hide the ID column
186 my $scrolled = Gtk2::ScrolledWindow->new;
187 $scrolled->set_policy('automatic', 'automatic');
188 $scrolled->add($list);
190 return $scrolled;
193 sub create_menu
195 my $menu_tree = [
196 _File => {
197 item_type => '<Branch>',
198 children => [
199 '_Test configuration' => {
200 callback => \&test_callback,
201 callback_action => 0,
203 Separator => {
204 item_type => '<Separator>',
206 _Quit => {
207 item_type => '<StockItem>',
208 callback => \&window_close,
209 extra_data => 'gtk-quit',
213 _Edit => {
214 item_type => '<Branch>',
215 children => [
216 '_New event' => {
217 item_type => '<StockItem>',
218 extra_data => 'gtk-new',
219 callback => \&new_callback,
221 '_Delete event' => {
222 item_type => '<StockItem>',
223 extra_data => 'gtk-delete',
224 accelerator => '<ctrl>D',
225 callback => \&delete_callback,
227 '_Edit reminders' => {
228 callback => \&edit_callback,
230 Separator => {
231 item_type => '<Separator>',
233 '_Preferences...' => {
234 item_type => '<StockItem>',
235 extra_data => 'gtk-preferences',
236 callback => \&prefs_callback,
242 return Gtk2::SimpleMenu->new(menu_tree => $menu_tree);
245 sub test_callback
247 unless (-x $SEND_REMINDERS_EXECUTABLE) {
248 my $error = Gtk2::MessageDialog->new($window, ['modal'], 'error', 'ok', "Cannot run '$SEND_REMINDERS_EXECUTABLE'. Check your installation.");
249 $error->run();
250 $error->destroy();
251 return;
254 save_data();
255 my $errorOutput = `$SEND_REMINDERS_EXECUTABLE`;
256 if ($errorOutput) {
257 my $error = Gtk2::MessageDialog->new($window, ['modal'], 'error', 'ok', $errorOutput);
258 $error->run();
259 $error->destroy();
260 return;
262 return 1;
265 sub new_callback
267 my (undef, $listbox) = get_selected_index();
269 my $event_type = $listbox->{type};
270 $events->add_event($event_type);
272 my $event_index = $listbox->get_model()->get_nb_events() - 1;
273 $listbox->get_selection()->select_path(Gtk2::TreePath->new_from_string($event_index));
274 return 1;
277 sub delete_callback
279 my ($path, $listbox) = get_selected_index();
280 return unless defined($path);
282 $listbox->get_model()->delete_event($path);
283 return 1;
286 sub edit_callback
288 my $selected = get_selected_event();
289 return unless defined($selected);
291 my $dialog = create_reminder_dialog($selected);
292 $dialog->show_all;
293 return 1;
296 sub prefs_callback
298 my $dialog = create_prefs_dialog();
299 $dialog->run();
300 $dialog->destroy();
301 return 1;
304 sub create_reminder_dialog
306 my ($event) = @_;
308 my $name = $event->get_name();
309 my $reminders = $event->get_reminders();
311 my $dialog = Gtk2::Dialog->new_with_buttons('Edit reminders', $window,
312 'destroy-with-parent',
313 'gtk-close' => 'close' );
315 my $reminder_label = Gtk2::Label->new("Current reminders for '$name':");
316 my $cb_sameday = Gtk2::CheckButton->new("Same day");
317 my $cb_advance = Gtk2::CheckButton->new("Days in advance:");
318 my $adj = Gtk2::Adjustment->new(1.0, 1.0, 364, 1.0, 10.0, 0.0);
319 my $spin_days = Gtk2::SpinButton->new($adj, 0, 0);
321 # Disable spin button unless the option is checked
322 $spin_days->set_sensitive(FALSE);
323 $cb_advance->signal_connect(toggled => sub {
324 $spin_days->set_sensitive($cb_advance->get_active());
327 my $hbox = Gtk2::HBox->new();
328 $hbox->add($cb_advance);
329 $hbox->add($spin_days);
331 my $checkboxes = Gtk2::VBox->new(FALSE, 6);
332 $checkboxes->set_border_width(6);
333 $checkboxes->add($reminder_label);
334 $checkboxes->add($cb_sameday);
335 $checkboxes->add($hbox);
337 foreach my $reminder (@$reminders)
339 if ($reminder == 0)
341 $cb_sameday->set_active(TRUE);
343 elsif ($reminder > 0)
345 $cb_advance->set_active(TRUE);
346 $spin_days->set_value($reminder);
350 $checkboxes->show_all;
351 $dialog->vbox->add($checkboxes);
353 $dialog->signal_connect(response => sub {
354 # Update values inside EventList
355 my @new_reminders = ();
356 push(@new_reminders, 0) if $cb_sameday->get_active();
357 push(@new_reminders, $spin_days->get_value())
358 if $cb_advance->get_active();
359 $event->set_reminders(\@new_reminders);
361 $_[0]->destroy;
362 });
364 return $dialog;
367 sub create_prefs_dialog
369 my $dialog = Gtk2::Dialog->new_with_buttons('Preferences', $window,
370 'modal',
371 'gtk-close' => 'close' );
373 my $info_label = Gtk2::Label->new("Set the default recipient for the reminder emails:");
374 my $name_label = Gtk2::Label->new($NAME_PREF_LABEL);
375 my $email_label = Gtk2::Label->new($EMAIL_PREF_LABEL);
377 my $fname = Gtk2::Entry->new();
378 my $lname = Gtk2::Entry->new();
379 my $email = Gtk2::Entry->new();
380 my @fullname = $events->get_user_name();
381 $fname->set_text($fullname[0]);
382 $lname->set_text($fullname[1]);
383 $email->set_text($events->get_user_email());
385 my $author_wishes = Gtk2::CheckButton->new_with_label('Send birthday wishes to the email-reminder author?');
386 my $existing_value = $events->get_author_wishes();
387 if (defined($existing_value)) {
388 $author_wishes->set_active($existing_value);
390 else {
391 # Default to true in the UI
392 $author_wishes->set_active(TRUE);
395 my $name = Gtk2::HBox->new();
396 $name->pack_start($fname, TRUE, TRUE, 0);
397 $name->pack_start($lname, TRUE, TRUE, 0);
399 my $options = Gtk2::Table->new(3, 2, FALSE);
400 $options->set_row_spacings(3);
401 $options->attach_defaults($name_label, 0, 1, 0, 1);
402 $options->attach_defaults($name, 1, 2, 0, 1);
403 $options->attach_defaults($email_label, 0, 1, 1, 2);
404 $options->attach_defaults($email, 1, 2, 1, 2);
406 $options->show_all();
407 $info_label->show();
408 $author_wishes->show();
409 $dialog->vbox->set_spacing(6);
410 $dialog->vbox->add($info_label);
411 $dialog->vbox->add($options);
412 $dialog->vbox->add($author_wishes);
414 $dialog->signal_connect(response => sub {
415 # Update values
416 $events->set_user_fname($fname->get_text());
417 $events->set_user_lname($lname->get_text());
418 $events->set_author_wishes($author_wishes->get_active() ? '1' : '0');
419 unless ($events->set_user_email($email->get_text())) {
420 my $warning = Gtk2::MessageDialog->new($dialog, ['modal'], 'warning', 'ok', "The email address you entered is invalid; it has not been changed.");
421 $warning->run();
422 $warning->destroy();
426 return $dialog;
429 sub window_close
431 unless ($events->get_user_email())
433 my $warning = Gtk2::MessageDialog->new($window, ['modal'], 'warning', 'none', "You will not receive any reminders since you have not set your email address. \n\nWould you like to set your email address in the preferences now or quit?");
434 $warning->add_buttons('gtk-preferences' => 'no', 'gtk-quit' => 'yes');
436 my $response = $warning->run();
437 $warning->destroy();
439 if ('no' eq $response)
441 prefs_callback();
442 return TRUE;
446 $window->destroy();
447 return 1;
450 sub init_ui
452 $window = Gtk2::Window->new;
453 $window->set_title($WINDOW_TITLE);
454 $window->set_default_size($DEFAULT_WIDTH, $DEFAULT_HEIGHT);
455 $window->set_resizable(TRUE);
457 $window->signal_connect(destroy => sub { Gtk2->main_quit; });
458 $window->signal_connect(delete_event => \&window_close);
460 my $vbox = Gtk2::VBox->new(FALSE, 0);
461 $window->add($vbox);
463 # Menu
464 my $menu = create_menu();
465 $window->add_accel_group($menu->{accel_group});
466 $vbox->pack_start($menu->{widget}, FALSE, FALSE, 0);
468 # Toolbar
469 my $toolbar = Gtk2::Toolbar->new() ;
470 $toolbar->set_style('both-horiz');
471 $toolbar->insert_stock('gtk-new', "Add a new event", undef, \&new_callback, undef, -1);
472 $toolbar->insert_stock('gtk-delete', "Delete the selected event", undef, \&delete_callback, undef, -1);
473 $toolbar->append_space();
474 $toolbar->insert_item("Edit reminders", "Edit reminders for the selected event", undef, undef, \&edit_callback, undef, -1);
475 $vbox->pack_start($toolbar, FALSE, FALSE, 0);
477 # Tabs
478 # TODO: make the accel Ctrl+PageDown/Up work everywhere
479 # (not just when focus is on the tabs)
480 $notebook = Gtk2::Notebook->new();
481 $notebook->set_tab_pos('top');
482 $vbox->pack_start($notebook, TRUE, TRUE, 0);
484 # Lists
485 my $listbox1 = create_listbox("birthday", 'Name', 'Birth date', 'Email');
486 my $listbox2 = create_listbox("anniversary", 'Person 1', 'Wedding date', 'Email 1', 'Person 2', 'Email 2');
487 my $listbox3 = create_listbox("yearly", 'Event name', 'Event date');
488 my $listbox4 = create_listbox("monthly", 'Event name', 'Event day');
489 my $listbox5 = create_listbox("weekly", 'Event name', 'Event day');
490 $notebook->append_page($listbox1, $BIRTHDAY_TAB);
491 $notebook->append_page($listbox2, $ANNIVERSARY_TAB);
492 $notebook->append_page($listbox3, $YEARLY_TAB);
493 $notebook->append_page($listbox4, $MONTHLY_TAB);
494 $notebook->append_page($listbox5, $WEEKLY_TAB);
496 return 1;
499 sub get_selected_index
501 my $scrolled = $notebook->get_nth_page($notebook->get_current_page());
502 my $listbox = $scrolled->get_child();
504 my $path = $listbox->get_selection()->get_selected_rows();
505 return ($path, $listbox);
508 sub get_selected_event
510 my ($path, $listbox) = get_selected_index();
511 return unless defined($path);
513 my $event = $listbox->get_model()->get_event($path);
514 return $event;
517 sub run_gui
519 $window->show_all();
520 Gtk2->main;
521 return 1;
524 sub main
526 print "Version: $EmailReminder::Utils::VERSION\n" if $verbose;
527 load_data();
528 init_ui();
529 run_gui();
530 save_data();
531 return 1;
534 if ($help || $version) {
535 print "$WINDOW_TITLE $EmailReminder::Utils::VERSION\n";
536 if ($help) {
537 print "\n";
538 pod2usage(1);
539 } else {
540 exit(1);
542 } else {
543 main();