GnuCash  2.6.99
 All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Groups Pages
gnc-frequency.c
1 /********************************************************************\
2  * gnc-frequency.c -- GnuCash widget for frequency editing. *
3  * Copyright (C) 2001,2002,2007 Joshua Sled <[email protected]>*
4  * Copyright (C) 2003 Linas Vepstas <[email protected]> *
5  * Copyright (C) 2006 David Hampton <[email protected]> *
6  * Copyright (C) 2011 Robert Fewell *
7  * *
8  * This program is free software; you can redistribute it and/or *
9  * modify it under the terms of the GNU General Public License as *
10  * published by the Free Software Foundation; either version 2 of *
11  * the License, or (at your option) any later version. *
12  * *
13  * This program is distributed in the hope that it will be useful, *
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of *
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
16  * GNU General Public License for more details. *
17  * *
18  * You should have received a copy of the GNU General Public License*
19  * along with this program; if not, contact: *
20  * *
21  * Free Software Foundation Voice: +1-617-542-5942 *
22  * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 *
23  * Boston, MA 02110-1301, USA [email protected] *
24  * *
25 \********************************************************************/
26 
27 #include "config.h"
28 
29 #include <glib.h>
30 #include <gtk/gtk.h>
31 #include <math.h>
32 #include <time.h>
33 
34 #include "dialog-utils.h"
35 #include "gnc-component-manager.h"
36 #include "gnc-engine.h"
37 #include "gnc-frequency.h"
38 #include "FreqSpec.h"
39 #include "gnc-ui-util.h"
40 
41 #undef G_LOG_DOMAIN
42 #define G_LOG_DOMAIN "gnc.gui.frequency"
43 
44 static QofLogModule log_module = GNC_MOD_GUI;
45 
46 #define LAST_DAY_OF_MONTH_OPTION_INDEX 31
47 
50 typedef enum
51 {
52  GNCFREQ_CHANGED,
53  LAST_SIGNAL
54 } GNCF_Signals;
55 
56 static guint gnc_frequency_signals[LAST_SIGNAL] = { 0 };
57 
60 static void gnc_frequency_class_init( GncFrequencyClass *klass );
61 static void gnc_frequency_class_destroy( GtkObject *object );
62 
63 static void freq_combo_changed( GtkComboBox *b, gpointer d );
64 static void start_date_changed( GNCDateEdit *gde, gpointer d );
65 static void spin_changed_helper( GtkAdjustment *adj, gpointer d );
66 
67 static void weekly_days_changed( GtkButton *b, gpointer d );
68 
69 static void monthly_sel_changed( GtkButton *b, gpointer d );
70 static void semimonthly_sel_changed( GtkButton *b, gpointer d );
71 
74 static GObjectClass *parent_class = NULL;
75 
76 enum
77 {
78  PAGE_NONE = 0,
79  PAGE_ONCE,
80  PAGE_DAILY,
81  PAGE_WEEKLY,
82  PAGE_SEMI_MONTHLY,
83  PAGE_MONTHLY
84 };
85 
86 static const struct pageDataTuple PAGES[] =
87 {
88  { PAGE_NONE, UIFREQ_NONE, "None" },
89  { PAGE_ONCE, UIFREQ_ONCE, "Once" },
90  { PAGE_DAILY, UIFREQ_DAILY, "Daily" },
91  { PAGE_WEEKLY, UIFREQ_WEEKLY, "Weekly" },
92  { PAGE_SEMI_MONTHLY, UIFREQ_SEMI_MONTHLY, "Semi-Monthly" },
93  { PAGE_MONTHLY, UIFREQ_MONTHLY, "Monthly" },
94  { 0, 0, 0 }
95 };
96 
97 static const char *CHECKBOX_NAMES[] =
98 {
99  "wd_check_sun",
100  "wd_check_mon",
101  "wd_check_tue",
102  "wd_check_wed",
103  "wd_check_thu",
104  "wd_check_fri",
105  "wd_check_sat",
106  NULL
107 };
108 
111 GType
112 gnc_frequency_get_type()
113 {
114  static GType gncfreq_type = 0;
115  if (gncfreq_type == 0)
116  {
117  static GTypeInfo gncfreq_info =
118  {
119  sizeof(GncFrequencyClass),
120  NULL,
121  NULL,
122  (GClassInitFunc)gnc_frequency_class_init,
123  NULL,
124  NULL,
125  sizeof(GncFrequency),
126  0,
127  (GInstanceInitFunc)gnc_frequency_init
128  };
129 
130  gncfreq_type = g_type_register_static (GTK_TYPE_VBOX,
131  "GncFrequency",
132  &gncfreq_info, 0);
133  }
134 
135  return gncfreq_type;
136 }
137 
138 
139 static void
140 gnc_frequency_class_init( GncFrequencyClass *klass )
141 {
142  GObjectClass *object_class;
143  GtkObjectClass *gtkobject_class;
144 
145  parent_class = g_type_class_peek_parent (klass);
146 
147  object_class = G_OBJECT_CLASS (klass);
148  gtkobject_class = GTK_OBJECT_CLASS (klass);
149 
150  gnc_frequency_signals[GNCFREQ_CHANGED] =
151  g_signal_new ("changed",
152  G_OBJECT_CLASS_TYPE (object_class),
153  G_SIGNAL_RUN_FIRST,
154  G_STRUCT_OFFSET (GncFrequencyClass, changed),
155  NULL,
156  NULL,
157  g_cclosure_marshal_VOID__VOID,
158  G_TYPE_NONE,
159  0);
160 
161  /* GtkObject signals */
162  gtkobject_class->destroy = gnc_frequency_class_destroy;
163 }
164 
165 
166 void
167 gnc_frequency_init(GncFrequency *gf)
168 {
169  int i;
170  GtkVBox* vb;
171  GtkWidget* o;
172  GtkAdjustment* adj;
173  GtkBuilder *builder;
174 
175  static const struct comboBoxTuple
176  {
177  char *name;
178  void (*fn)();
179  } comboBoxes[] =
180  {
181  { "freq_combobox", freq_combo_changed },
182  { "semimonthly_first", semimonthly_sel_changed },
183  { "semimonthly_first_weekend", semimonthly_sel_changed },
184  { "semimonthly_second", semimonthly_sel_changed },
185  { "semimonthly_second_weekend", semimonthly_sel_changed },
186  { "monthly_day", monthly_sel_changed },
187  { "monthly_weekend", monthly_sel_changed },
188  { NULL, NULL }
189  };
190 
191  static const struct spinvalTuple
192  {
193  char *name;
194  void (*fn)();
195  } spinVals[] =
196  {
197  { "daily_spin", spin_changed_helper },
198  { "weekly_spin", spin_changed_helper },
199  { "semimonthly_spin", spin_changed_helper },
200  { "monthly_spin", spin_changed_helper },
201  { NULL, NULL }
202  };
203 
204  builder = gtk_builder_new();
205  gnc_builder_add_from_file (builder , "gnc-frequency.glade", "adjustment1");
206  gnc_builder_add_from_file (builder , "gnc-frequency.glade", "adjustment2");
207  gnc_builder_add_from_file (builder , "gnc-frequency.glade", "adjustment3");
208  gnc_builder_add_from_file (builder , "gnc-frequency.glade", "adjustment4");
209  gnc_builder_add_from_file (builder , "gnc-frequency.glade", "liststore1");
210  gnc_builder_add_from_file (builder , "gnc-frequency.glade", "liststore2");
211  gnc_builder_add_from_file (builder , "gnc-frequency.glade", "liststore3");
212  gnc_builder_add_from_file (builder , "gnc-frequency.glade", "liststore4");
213  gnc_builder_add_from_file (builder , "gnc-frequency.glade", "liststore5");
214  gnc_builder_add_from_file (builder , "gnc-frequency.glade", "liststore6");
215  gnc_builder_add_from_file (builder , "gnc-frequency.glade", "liststore7");
216  gnc_builder_add_from_file (builder , "gnc-frequency.glade", "gncfreq_vbox");
217 
218  gf->builder = builder;
219  o = GTK_WIDGET(gtk_builder_get_object (builder, "gncfreq_nb"));
220  gf->nb = GTK_NOTEBOOK(o);
221  o = GTK_WIDGET(gtk_builder_get_object (builder, "freq_combobox"));
222  gf->freqComboBox = GTK_COMBO_BOX(o);
223  gf->startDate = GNC_DATE_EDIT(gnc_date_edit_new(time(NULL), FALSE, FALSE));
224  /* Add the new widget to the table. */
225  {
226  gint dont_expand_or_fill = 0;
227  GtkWidget *table = GTK_WIDGET(gtk_builder_get_object (builder, "gncfreq_table"));
228  gtk_table_attach(GTK_TABLE(table), GTK_WIDGET(gf->startDate),
229  4, 5, 0, 1, dont_expand_or_fill, 0,
230  0, 0);
231  }
232  vb = GTK_VBOX(gtk_builder_get_object (builder, "gncfreq_vbox"));
233  gf->vb = vb;
234  gtk_container_add(GTK_CONTAINER(&gf->widget), GTK_WIDGET(gf->vb));
235 
236  /* initialize the combo boxes */
237  for (i = 0; comboBoxes[i].name != NULL; i++)
238  {
239  o = GTK_WIDGET(gtk_builder_get_object (builder, comboBoxes[i].name));
240  gtk_combo_box_set_active(GTK_COMBO_BOX(o), 0);
241  if (comboBoxes[i].fn != NULL)
242  {
243  g_signal_connect(o, "changed", G_CALLBACK(comboBoxes[i].fn), gf);
244  }
245  }
246 
247  /* initialize the spin buttons */
248  for (i = 0; spinVals[i].name != NULL; i++)
249  {
250  if (spinVals[i].fn != NULL)
251  {
252  o = GTK_WIDGET(gtk_builder_get_object (builder, spinVals[i].name));
253  adj = gtk_spin_button_get_adjustment(GTK_SPIN_BUTTON(o));
254  g_signal_connect(adj, "value_changed", G_CALLBACK(spinVals[i].fn), gf);
255  }
256  }
257 
258  /* initialize the weekly::day-of-week checkbox-selection hooks */
259  for (i = 0; i < 7; i++)
260  {
261  o = GTK_WIDGET(gtk_builder_get_object (builder, CHECKBOX_NAMES[i]));
262  g_signal_connect(o, "clicked",
263  G_CALLBACK(weekly_days_changed), gf);
264  }
265 
266  gtk_widget_show_all(GTK_WIDGET(&gf->widget));
267 
268  /* respond to start date changes */
269  g_signal_connect(gf->startDate, "date_changed", G_CALLBACK(start_date_changed), gf);
270 
271  gtk_builder_connect_signals_full (builder, gnc_builder_connect_full_func, gf);
272 
273 }
274 
275 
283 static void
284 gnc_frequency_class_destroy (GtkObject *object)
285 {
286  GncFrequency *gf;
287 
288  ENTER("frequency %p", object);
289  g_return_if_fail (object != NULL);
290  g_return_if_fail (GNC_IS_FREQUENCY (object));
291 
292  gf = GNC_FREQUENCY (object);
293 
294  if (gf->builder)
295  {
296  DEBUG("removing builder");
297  g_object_unref(G_OBJECT(gf->builder));
298  gf->builder = NULL;
299  }
300 
301  if (GTK_OBJECT_CLASS (parent_class)->destroy)
302  GTK_OBJECT_CLASS (parent_class)->destroy (object);
303  LEAVE(" ");
304 }
305 
306 
307 static void
308 spin_changed_helper( GtkAdjustment *adj, gpointer d )
309 {
310  g_signal_emit_by_name(GNC_FREQUENCY(d), "changed");
311 }
312 
313 
314 static void
315 weekly_days_changed( GtkButton *b, gpointer d )
316 {
317  g_signal_emit_by_name(GNC_FREQUENCY(d), "changed");
318 }
319 
320 
321 static void
322 monthly_sel_changed( GtkButton *b, gpointer d )
323 {
324  g_signal_emit_by_name(GNC_FREQUENCY(d), "changed");
325 }
326 
327 
328 static void
329 semimonthly_sel_changed( GtkButton *b, gpointer d )
330 {
331  g_signal_emit_by_name(GNC_FREQUENCY(d), "changed");
332 }
333 
334 
335 static void
336 freq_combo_changed(GtkComboBox *b, gpointer d)
337 {
338  GncFrequency *gf = GNC_FREQUENCY(d);
339  int option_index;
340 
341  /* Set the new page. */
342  option_index = gtk_combo_box_get_active(GTK_COMBO_BOX(gf->freqComboBox));
343  gtk_notebook_set_current_page(gf->nb, option_index);
344  g_signal_emit_by_name(gf, "changed");
345 }
346 
347 
348 static void
349 start_date_changed( GNCDateEdit *gde, gpointer d )
350 {
351  g_signal_emit_by_name(GNC_FREQUENCY(d), "changed");
352 }
353 
354 
355 /**************************************
356  * Relabel some of the labels *
357  *************************************/
358 void
359 gnc_frequency_set_frequency_label_text(GncFrequency *gf, const gchar *txt)
360 {
361  GtkLabel *lbl;
362  if (!gf || !txt) return;
363  lbl = GTK_LABEL (gtk_builder_get_object (gf->builder, "freq_label"));
364  gtk_label_set_text (lbl, txt);
365 }
366 
367 
368 void
369 gnc_frequency_set_date_label_text(GncFrequency *gf, const gchar *txt)
370 {
371  GtkLabel *lbl;
372  if (!gf || !txt) return;
373  lbl = GTK_LABEL (gtk_builder_get_object (gf->builder, "startdate_label"));
374  gtk_label_set_text (lbl, txt);
375 }
376 
377 
378 GtkWidget*
379 gnc_frequency_new_from_recurrence(GList *recurrences, const GDate *start_date)
380 {
381  return gnc_frequency_new(recurrences, start_date);
382 }
383 
384 
385 GtkWidget*
386 gnc_frequency_new(GList *recurrences, const GDate *start_date)
387 {
388  GncFrequency *toRet;
389  toRet = g_object_new(gnc_frequency_get_type(), NULL);
390  gnc_frequency_setup_recurrence(toRet, recurrences, start_date);
391  return GTK_WIDGET(toRet);
392 }
393 
394 
395 static void
396 _setup_weekly_recurrence(GncFrequency *gf, Recurrence *r)
397 {
398  GDate recurrence_date;
399  GDateWeekday day_of_week;
400  guint multiplier = recurrenceGetMultiplier(r);
401  const char *checkbox_widget_name;
402  GtkWidget *weekday_checkbox;
403 
404  GtkWidget *multipler_spin = GTK_WIDGET(gtk_builder_get_object (gf->builder, "weekly_spin"));
405  gtk_spin_button_set_value(GTK_SPIN_BUTTON(multipler_spin), multiplier);
406 
407  recurrence_date = recurrenceGetDate(r);
408  day_of_week = g_date_get_weekday(&recurrence_date);
409  g_assert(day_of_week >= G_DATE_MONDAY && day_of_week <= G_DATE_SUNDAY);
410  // this `mod 7' is explicit knowledge of the values of (monday=1)-based
411  // GDateWeekday, vs. our (sunday=0)-based checkbox names array.
412  checkbox_widget_name = CHECKBOX_NAMES[day_of_week % 7];
413  weekday_checkbox = GTK_WIDGET(gtk_builder_get_object (gf->builder, checkbox_widget_name));
414  gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(weekday_checkbox), TRUE);
415 }
416 
417 
418 static int
419 _get_monthly_combobox_index(Recurrence *r)
420 {
421  GDate recurrence_date = recurrenceGetDate(r);
422  int week = 0;
423  int day_of_month_index = g_date_get_day(&recurrence_date) - 1;
424  if (recurrenceGetPeriodType(r) == PERIOD_END_OF_MONTH)
425  {
426  day_of_month_index = LAST_DAY_OF_MONTH_OPTION_INDEX;
427  }
428  else if (recurrenceGetPeriodType(r) == PERIOD_LAST_WEEKDAY)
429  {
430  day_of_month_index
431  = LAST_DAY_OF_MONTH_OPTION_INDEX
432  + g_date_get_weekday(&recurrence_date);
433  }
434  else if (recurrenceGetPeriodType(r) == PERIOD_NTH_WEEKDAY)
435  {
436  week = day_of_month_index / 7 > 3 ? 3 : day_of_month_index / 7;
437  if (week > 0 && day_of_month_index % 7 == 0)
438  --week;
439  day_of_month_index = LAST_DAY_OF_MONTH_OPTION_INDEX + 7 +
440  g_date_get_weekday(&recurrence_date) + 7 * week;
441 
442 
443  }
444  /* else { default value } */
445  return day_of_month_index;
446 }
447 
448 
449 void
450 gnc_frequency_setup_recurrence(GncFrequency *gf, GList *recurrences, const GDate *start_date)
451 {
452  gnc_frequency_setup(gf, recurrences, start_date);
453 }
454 
455 
456 void
457 gnc_frequency_setup(GncFrequency *gf, GList *recurrences, const GDate *start_date)
458 {
459  gboolean made_changes = FALSE;
460 
461  // setup start-date, if present
462  if (start_date != NULL
463  && g_date_valid(start_date))
464  {
465  gnc_date_edit_set_gdate(gf->startDate, start_date);
466  made_changes = TRUE;
467  }
468 
469  if (recurrences == NULL)
470  {
471  goto maybe_signal;
472  // return...
473  }
474 
475  if (g_list_length(recurrences) > 1)
476  {
477  if (recurrenceListIsWeeklyMultiple(recurrences))
478  {
479  for (; recurrences != NULL; recurrences = recurrences->next)
480  {
481  _setup_weekly_recurrence(gf, (Recurrence*)recurrences->data);
482  }
483 
484  gtk_notebook_set_current_page(gf->nb, PAGE_WEEKLY);
485  gtk_combo_box_set_active(gf->freqComboBox, PAGE_WEEKLY);
486  }
487  else if (recurrenceListIsSemiMonthly(recurrences))
488  {
489  Recurrence *first, *second;
490  GtkWidget *multiplier_spin;
491  GtkWidget *dom_combobox;
492 
493  first = (Recurrence*)g_list_nth_data(recurrences, 0);
494  second = (Recurrence*)g_list_nth_data(recurrences, 1);
495 
496  multiplier_spin = GTK_WIDGET(gtk_builder_get_object (gf->builder, "semimonthly_spin"));
497  gtk_spin_button_set_value(GTK_SPIN_BUTTON(multiplier_spin), recurrenceGetMultiplier(first));
498  dom_combobox = GTK_WIDGET(gtk_builder_get_object (gf->builder, "semimonthly_first"));
499  gtk_combo_box_set_active(GTK_COMBO_BOX(dom_combobox), _get_monthly_combobox_index(first));
500  dom_combobox = GTK_WIDGET(gtk_builder_get_object (gf->builder, "semimonthly_first_weekend"));
501  gtk_combo_box_set_active(GTK_COMBO_BOX(dom_combobox), recurrenceGetWeekendAdjust(first));
502  dom_combobox = GTK_WIDGET(gtk_builder_get_object (gf->builder, "semimonthly_second"));
503  gtk_combo_box_set_active(GTK_COMBO_BOX(dom_combobox), _get_monthly_combobox_index(second));
504  dom_combobox = GTK_WIDGET(gtk_builder_get_object (gf->builder, "semimonthly_second_weekend"));
505  gtk_combo_box_set_active(GTK_COMBO_BOX(dom_combobox), recurrenceGetWeekendAdjust(second));
506 
507  gtk_notebook_set_current_page(gf->nb, PAGE_SEMI_MONTHLY);
508  gtk_combo_box_set_active(gf->freqComboBox, PAGE_SEMI_MONTHLY);
509  }
510  else
511  {
512  g_error("unknown composite recurrence with [%d] entries", g_list_length(recurrences));
513  }
514  }
515  else
516  {
517  Recurrence *r = (Recurrence*)recurrences->data;
518  g_debug("recurrence period [%d]", recurrenceGetPeriodType(r));
519  switch (recurrenceGetPeriodType(r))
520  {
521  case PERIOD_ONCE:
522  {
523  GDate recurrence_date = recurrenceGetDate(r);
524  if (g_date_compare(start_date, &recurrence_date) != 0)
525  {
526  char start_date_str[128], recur_date_str[128];
527  g_date_strftime(start_date_str, 127, "%x", start_date);
528  g_date_strftime(recur_date_str, 127, "%x", &recurrence_date);
529  g_critical("start_date [%s] != recurrence_date [%s]", start_date_str, recur_date_str);
530  }
531 
532  gtk_notebook_set_current_page(gf->nb, PAGE_ONCE);
533  gtk_combo_box_set_active(gf->freqComboBox, PAGE_ONCE);
534  }
535  break;
536  case PERIOD_DAY:
537  {
538  guint multiplier;
539  GtkWidget *spin_button;
540 
541  multiplier = recurrenceGetMultiplier(r);
542  spin_button = GTK_WIDGET(gtk_builder_get_object (gf->builder, "daily_spin"));
543  gtk_spin_button_set_value(GTK_SPIN_BUTTON(spin_button), multiplier);
544  made_changes = TRUE;
545 
546  gtk_notebook_set_current_page(gf->nb, PAGE_DAILY);
547  gtk_combo_box_set_active(gf->freqComboBox, PAGE_DAILY);
548  }
549  break;
550  case PERIOD_WEEK:
551  {
552  _setup_weekly_recurrence(gf, r);
553  gtk_notebook_set_current_page(gf->nb, PAGE_WEEKLY);
554  gtk_combo_box_set_active(gf->freqComboBox, PAGE_WEEKLY);
555  }
556  break;
557  case PERIOD_END_OF_MONTH:
558  case PERIOD_MONTH:
559  case PERIOD_YEAR:
560  case PERIOD_LAST_WEEKDAY:
561  case PERIOD_NTH_WEEKDAY:
562  {
563  guint multiplier;
564  GtkWidget *multipler_spin, *day_of_month, *weekend_mode;
565 
566  multipler_spin = GTK_WIDGET(gtk_builder_get_object (gf->builder, "monthly_spin"));
567  multiplier = recurrenceGetMultiplier(r);
568  if (recurrenceGetPeriodType(r) == PERIOD_YEAR)
569  multiplier *= 12;
570  gtk_spin_button_set_value(GTK_SPIN_BUTTON(multipler_spin), multiplier);
571 
572  day_of_month = GTK_WIDGET(gtk_builder_get_object (gf->builder, "monthly_day"));
573  gtk_combo_box_set_active(GTK_COMBO_BOX(day_of_month), _get_monthly_combobox_index(r));
574  weekend_mode = GTK_WIDGET(gtk_builder_get_object (gf->builder, "monthly_weekend"));
575  gtk_combo_box_set_active(GTK_COMBO_BOX(weekend_mode), recurrenceGetWeekendAdjust(r));
576 
577  gtk_notebook_set_current_page(gf->nb, PAGE_MONTHLY);
578  gtk_combo_box_set_active(gf->freqComboBox, PAGE_MONTHLY);
579  }
580  break;
581  default:
582  g_error("unknown recurrence period type [%d]", recurrenceGetPeriodType(r));
583  break;
584  }
585  }
586 
587 maybe_signal:
588  if (made_changes)
589  g_signal_emit_by_name(gf, "changed");
590 }
591 
592 
593 static gint
594 _get_multiplier_from_widget(GncFrequency *gf, char *widget_name)
595 {
596  GtkWidget *multiplier_spin;
597  multiplier_spin = GTK_WIDGET(gtk_builder_get_object (gf->builder, widget_name));
598  return gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(multiplier_spin));
599 }
600 
601 
602 static Recurrence*
603 _get_day_of_month_recurrence(GncFrequency *gf, GDate *start_date, int multiplier, char *combo_name, char *combo_weekend_name)
604 {
605  Recurrence *r;
606  GtkWidget *day_of_month_combo = GTK_WIDGET(gtk_builder_get_object (gf->builder, combo_name));
607  int day_of_month_index = gtk_combo_box_get_active(GTK_COMBO_BOX(day_of_month_combo));
608  GtkWidget *weekend_adjust_combo = GTK_WIDGET(gtk_builder_get_object (gf->builder, combo_weekend_name));
609  int weekend_adjust = gtk_combo_box_get_active(GTK_COMBO_BOX(weekend_adjust_combo));
610  GDateWeekday selected_day_of_week;
611  GDate *day_of_week_date;
612  int selected_index, selected_week;
613  r = g_new0(Recurrence, 1);
614  if (day_of_month_index > LAST_DAY_OF_MONTH_OPTION_INDEX + 7)
615  {
616  selected_index = day_of_month_index - LAST_DAY_OF_MONTH_OPTION_INDEX - 7;
617  day_of_week_date = g_date_new_julian(g_date_get_julian(start_date));
618  selected_week = (selected_index - 1) / 7 == 4 ? 3 : (selected_index - 1) / 7;
619  selected_day_of_week = selected_index - 7 * selected_week;
620  g_date_set_day(day_of_week_date, 1);
621  while (g_date_get_weekday(day_of_week_date) != selected_day_of_week)
622  g_date_add_days(day_of_week_date, 1);
623  g_date_add_days(day_of_week_date, 7 * selected_week);
624  recurrenceSet(r, multiplier, PERIOD_NTH_WEEKDAY, day_of_week_date, WEEKEND_ADJ_NONE);
625  }
626  else if (day_of_month_index > LAST_DAY_OF_MONTH_OPTION_INDEX)
627  {
628  day_of_week_date = g_date_new_julian(g_date_get_julian(start_date));
629  selected_day_of_week = day_of_month_index - LAST_DAY_OF_MONTH_OPTION_INDEX;
630  // increment until we align on the DOW, but stay inside the month
631  g_date_set_day(day_of_week_date, 1);
632  while (g_date_get_weekday(day_of_week_date) != selected_day_of_week)
633  g_date_add_days(day_of_week_date, 1);
634  recurrenceSet(r, multiplier, PERIOD_LAST_WEEKDAY, day_of_week_date, weekend_adjust);
635  }
636  else if (day_of_month_index == LAST_DAY_OF_MONTH_OPTION_INDEX)
637  {
638  GDate *day_of_month = g_date_new_julian(g_date_get_julian(start_date));
639  recurrenceSet(r, multiplier, PERIOD_END_OF_MONTH, day_of_month, weekend_adjust);
640  }
641  else
642  {
643  int allowable_date = -1;
644  GDate *day_of_month = g_date_new_julian(g_date_get_julian(start_date));
645  allowable_date = MIN(day_of_month_index + 1,
646  g_date_get_days_in_month(g_date_get_month(day_of_month),
647  g_date_get_year(day_of_month)));
648  g_date_set_day(day_of_month, allowable_date);
649  recurrenceSet(r, multiplier, PERIOD_MONTH, day_of_month, weekend_adjust);
650  }
651  return r;
652 }
653 
654 
655 void
656 gnc_frequency_save_to_recurrence(GncFrequency *gf, GList **recurrences, GDate *out_start_date)
657 {
658  GDate start_date;
659  gint page_index;
660 
661  gnc_date_edit_get_gdate(gf->startDate, &start_date);
662  if (out_start_date != NULL)
663  *out_start_date = start_date;
664 
665  if (recurrences == NULL)
666  return;
667 
668  page_index = gtk_notebook_get_current_page(gf->nb);
669 
670  switch (page_index)
671  {
672  case PAGE_NONE:
673  {
674  // empty-recurrence list ~~ none.
675  } break;
676  case PAGE_ONCE:
677  {
678  Recurrence *r = g_new0(Recurrence, 1);
679  recurrenceSet(r, 1, PERIOD_ONCE, &start_date, WEEKEND_ADJ_NONE);
680  *recurrences = g_list_append(*recurrences, r);
681  }
682  break;
683  case PAGE_DAILY:
684  {
685  gint multiplier = _get_multiplier_from_widget(gf, "daily_spin");
686  Recurrence *r = g_new0(Recurrence, 1);
687  recurrenceSet(r, multiplier, PERIOD_DAY, &start_date, WEEKEND_ADJ_NONE);
688  *recurrences = g_list_append(*recurrences, r);
689  }
690  break;
691  case PAGE_WEEKLY:
692  {
693  int multiplier = _get_multiplier_from_widget(gf, "weekly_spin");
694  int checkbox_idx;
695  for (checkbox_idx = 0; CHECKBOX_NAMES[checkbox_idx] != NULL; checkbox_idx++)
696  {
697  GDate *day_of_week_aligned_date;
698  Recurrence *r;
699  const char *day_widget_name = CHECKBOX_NAMES[checkbox_idx];
700  GtkWidget *weekday_checkbox = GTK_WIDGET(gtk_builder_get_object (gf->builder, day_widget_name));
701 
702  if (!gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(weekday_checkbox)))
703  continue;
704 
705  day_of_week_aligned_date = g_date_new_julian(g_date_get_julian(&start_date));
706  // increment until we align on the DOW.
707  while ((g_date_get_weekday(day_of_week_aligned_date) % 7) != checkbox_idx)
708  g_date_add_days(day_of_week_aligned_date, 1);
709 
710  r = g_new0(Recurrence, 1);
711  recurrenceSet(r, multiplier, PERIOD_WEEK, day_of_week_aligned_date, WEEKEND_ADJ_NONE);
712 
713  *recurrences = g_list_append(*recurrences, r);
714  }
715  }
716  break;
717  case PAGE_SEMI_MONTHLY:
718  {
719  int multiplier = _get_multiplier_from_widget(gf, "semimonthly_spin");
720  *recurrences = g_list_append(*recurrences, _get_day_of_month_recurrence(gf, &start_date, multiplier, "semimonthly_first", "semimonthly_first_weekend"));
721  *recurrences = g_list_append(*recurrences, _get_day_of_month_recurrence(gf, &start_date, multiplier, "semimonthly_second", "semimonthly_second_weekend"));
722  }
723  break;
724  case PAGE_MONTHLY:
725  {
726  int multiplier = _get_multiplier_from_widget(gf, "monthly_spin");
727  Recurrence *r = _get_day_of_month_recurrence(gf, &start_date, multiplier, "monthly_day", "monthly_weekend");
728  *recurrences = g_list_append(*recurrences, r);
729  }
730  break;
731  default:
732  g_error("unknown page index [%d]", page_index);
733  break;
734  }
735 }
utility functions for the GnuCash UI
#define DEBUG(format, args...)
Definition: qoflog.h:255
#define ENTER(format, args...)
Definition: qoflog.h:261
Period / Date Frequency Specification.
All type declarations for the whole Gnucash engine.
#define LEAVE(format, args...)
Definition: qoflog.h:271
const gchar * QofLogModule
Definition: qofid.h:89