GnuCash  2.6.99
 All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Groups Pages
gnc-recurrence.c
1 /********************************************************************
2  * gnc-recurrence.c -- GncRecurrence is a minimal GUI for *
3  * specifying a Recurrence. *
4  * *
5  * You see, small is _nice_. :) *
6  * Copyright (C) 2005, Chris Shoemaker <[email protected]> *
7  * Copyright (C) 2011, Robert Fewell *
8  * *
9  * This program is free software; you can redistribute it and/or *
10  * modify it under the terms of the GNU General Public License as *
11  * published by the Free Software Foundation; either version 2 of *
12  * the License, or (at your option) any later version. *
13  * *
14  * This program is distributed in the hope that it will be useful, *
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of *
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
17  * GNU General Public License for more details. *
18  * *
19  * You should have received a copy of the GNU General Public License*
20  * along with this program; if not, contact: *
21  * *
22  * Free Software Foundation Voice: +1-617-542-5942 *
23  * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 *
24  * Boston, MA 02110-1301, USA [email protected] *
25  *******************************************************************/
26 
27 #include <gtk/gtk.h>
28 #include "config.h"
29 #include "dialog-utils.h"
30 #include "gnc-recurrence.h"
31 #include "gnc-date-edit.h"
32 #include "Recurrence.h"
33 #include "gnc-engine.h"
34 #include "gnc-gdate-utils.h"
35 
36 static QofLogModule log_module = GNC_MOD_GUI;
37 
39 {
40  GtkVBox widget;
41 
42  GtkWidget *gde_start;
43  GtkComboBox *gcb_period;
44  GtkCheckButton *gcb_eom;
45  GtkSpinButton *gsb_mult;
46  GtkCheckButton *nth_weekday;
47 
48  Recurrence recurrence;
49 };
50 
51 typedef struct
52 {
53  GtkVBoxClass parent_class;
54  void (*changed) (GncRecurrence *gr);
56 
57 typedef enum
58 {
59  GNCRECURRENCE_CHANGED,
60  LAST_SIGNAL
61 } GNCR_Signals;
62 
63 typedef enum
64 {
65  GNCR_DAY,
66  GNCR_WEEK,
67  GNCR_MONTH,
68  GNCR_YEAR,
69 } UIPeriodType;
70 
71 static GObjectClass *parent_class = NULL;
72 
73 
74 static UIPeriodType get_pt_ui(GncRecurrence *gr)
75 {
76  return (gtk_combo_box_get_active(gr->gcb_period));
77 }
78 
79 
80 static void set_pt_ui(GncRecurrence *gr, PeriodType pt)
81 {
82  UIPeriodType idx;
83  switch (pt)
84  {
85  case PERIOD_DAY:
86  idx = 0;
87  break;
88  case PERIOD_WEEK:
89  idx = 1;
90  break;
91  case PERIOD_MONTH:
92  case PERIOD_END_OF_MONTH:
93  case PERIOD_NTH_WEEKDAY:
94  case PERIOD_LAST_WEEKDAY:
95  idx = 2;
96  break;
97  case PERIOD_YEAR:
98  idx = 3;
99  break;
100  default:
101  return;
102  }
103  gtk_combo_box_set_active(gr->gcb_period, idx);
104 
105  gtk_toggle_button_set_active(
106  GTK_TOGGLE_BUTTON(gr->nth_weekday),
107  (pt == PERIOD_NTH_WEEKDAY || pt == PERIOD_LAST_WEEKDAY));
108 
109  gtk_toggle_button_set_active(
110  GTK_TOGGLE_BUTTON(gr->gcb_eom),
111  (pt == PERIOD_END_OF_MONTH || pt == PERIOD_LAST_WEEKDAY));
112 }
113 
114 
115 static gboolean
116 is_ambiguous_relative(const GDate *date)
117 {
118  GDateDay d;
119  guint8 dim;
120 
121  d = g_date_get_day(date);
122  dim = g_date_get_days_in_month(
123  g_date_get_month(date), g_date_get_year(date));
124  return ((d - 1) / 7 == 3) && (dim - d < 7);
125 }
126 
127 
128 static gboolean
129 is_ambiguous_absolute(const GDate *date)
130 {
131  return (g_date_is_last_of_month(date) &&
132  (g_date_get_day(date) < 31));
133 }
134 
135 
136 static void
137 something_changed( GtkWidget *wid, gpointer d )
138 {
139  UIPeriodType pt;
140  GDate start;
141  gboolean show_last, use_wd;
142  GncRecurrence *gr = GNC_RECURRENCE(d);
143 
144 
145  pt = get_pt_ui(gr);
146  gnc_date_edit_get_gdate(GNC_DATE_EDIT(gr->gde_start), &start);
147 
148  if (pt == GNCR_MONTH)
149  g_object_set(G_OBJECT(gr->nth_weekday), "visible", TRUE, NULL);
150  else
151  {
152  g_object_set(G_OBJECT(gr->nth_weekday), "visible", FALSE, NULL);
153  gtk_toggle_button_set_active(
154  GTK_TOGGLE_BUTTON(gr->nth_weekday), FALSE);
155  }
156  use_wd = gtk_toggle_button_get_active(
157  GTK_TOGGLE_BUTTON(gr->nth_weekday));
158  //TODO: change label
159 
160  /* The case under which we show the "end of month" flag is very
161  narrow, because we can almost always DTRT without it. */
162  if (pt == GNCR_MONTH)
163  {
164  if (use_wd)
165  show_last = is_ambiguous_relative(&start);
166  else
167  show_last = is_ambiguous_absolute(&start);
168  }
169  else
170  {
171  show_last = FALSE;
172  gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(gr->gcb_eom), FALSE);
173  }
174  g_object_set(G_OBJECT(gr->gcb_eom), "visible", show_last, NULL);
175 
176  g_signal_emit_by_name(d, "changed");
177 }
178 
179 
180 static void
181 gnc_recurrence_init( GncRecurrence *gr )
182 {
183  GtkVBox *vb;
184  GtkHBox *hb;
185  GtkWidget *w;
186  GtkBuilder *builder;
187 
188  recurrenceSet(&gr->recurrence, 1, PERIOD_MONTH, NULL, WEEKEND_ADJ_NONE);
189 
190  /* Open up the builder file */
191  builder = gtk_builder_new();
192  gnc_builder_add_from_file (builder, "gnc-recurrence.glade", "GCB_PeriodType_liststore");
193  gnc_builder_add_from_file (builder, "gnc-recurrence.glade", "GSB_Mult_Adj");
194  gnc_builder_add_from_file (builder, "gnc-recurrence.glade", "RecurrenceEntryVBox");
195 
196  vb = GTK_VBOX(gtk_builder_get_object (builder, "RecurrenceEntryVBox"));
197  hb = GTK_HBOX(gtk_builder_get_object (builder, "Startdate_hbox"));
198  w = gnc_date_edit_new (gnc_time (NULL), FALSE, FALSE);
199  gr->gde_start = w;
200  gtk_box_pack_start (GTK_BOX (hb), w, TRUE, TRUE, 0);
201  gtk_widget_show (w);
202 
203  gtk_widget_set_no_show_all(GTK_WIDGET(gr->gde_start), TRUE);
204  gr->gcb_period = GTK_COMBO_BOX(gtk_builder_get_object (builder, "GCB_PeriodType"));
205  gr->gsb_mult = GTK_SPIN_BUTTON(gtk_builder_get_object (builder, "GSB_Mult"));
206  gr->gcb_eom = GTK_CHECK_BUTTON(gtk_builder_get_object (builder, "GCB_EndOfMonth"));
207  gr->nth_weekday = GTK_CHECK_BUTTON(gtk_builder_get_object (builder, "GCB_NthWeekday"));
208  gtk_widget_set_no_show_all(GTK_WIDGET(gr->gcb_eom), TRUE);
209  gtk_widget_set_no_show_all(GTK_WIDGET(gr->nth_weekday), TRUE);
210 
211  gtk_container_add( GTK_CONTAINER(&gr->widget), GTK_WIDGET(vb) );
212 
213  gnc_recurrence_set(gr, &gr->recurrence);
214  something_changed( GTK_WIDGET(gr), gr);
215 
216  /* Setup the signals */
217  g_signal_connect( G_OBJECT(gr->gde_start), "date_changed",
218  G_CALLBACK(something_changed), gr );
219  g_signal_connect( G_OBJECT(gr->gcb_period), "changed",
220  G_CALLBACK(something_changed), gr );
221  g_signal_connect( G_OBJECT(gr->gsb_mult), "value-changed",
222  G_CALLBACK(something_changed), gr );
223  g_signal_connect( G_OBJECT(gr->gcb_eom), "toggled",
224  G_CALLBACK(something_changed), gr );
225  g_signal_connect( G_OBJECT(gr->nth_weekday), "toggled",
226  G_CALLBACK(something_changed), gr );
227 
228  gtk_widget_show_all( GTK_WIDGET(&gr->widget) );
229 
230  gtk_builder_connect_signals(builder, gr);
231  g_object_unref(G_OBJECT(builder));
232 }
233 
234 
235 void
236 gnc_recurrence_set(GncRecurrence *gr, const Recurrence *r)
237 {
238  PeriodType pt;
239  guint mult;
240  GDate start;
241 
242  g_return_if_fail(gr && r);
243  pt = recurrenceGetPeriodType(r);
244  mult = recurrenceGetMultiplier(r);
245  start = recurrenceGetDate(r);
246 
247  gtk_spin_button_set_value(gr->gsb_mult, (gdouble) mult);
248 
249  // is there some better way?
250  {
251  time64 t;
252  t = gnc_time64_get_day_start_gdate (&start);
253  gnc_date_edit_set_time (GNC_DATE_EDIT(gr->gde_start), t);
254  }
255 
256  set_pt_ui(gr, pt);
257 }
258 
259 
260 const Recurrence *
261 gnc_recurrence_get(GncRecurrence *gr)
262 {
263  guint mult;
264  UIPeriodType period;
265  PeriodType pt;
266  GDate start;
267  gboolean use_eom = FALSE, rel;
268 
269  mult = (guint) gtk_spin_button_get_value_as_int(gr->gsb_mult);
270  gnc_date_edit_get_gdate(GNC_DATE_EDIT(gr->gde_start), &start);
271  period = get_pt_ui(gr);
272 
273  switch (period)
274  {
275  case GNCR_DAY:
276  pt = PERIOD_DAY;
277  break;
278  case GNCR_WEEK:
279  pt = PERIOD_WEEK;
280  break;
281  case GNCR_MONTH:
282  rel = gtk_toggle_button_get_active(
283  GTK_TOGGLE_BUTTON(gr->nth_weekday));
284  if (rel)
285  {
286  if (is_ambiguous_relative(&start))
287  {
288  use_eom = gtk_toggle_button_get_active(
289  GTK_TOGGLE_BUTTON(gr->gcb_eom));
290  }
291  else
292  {
293  GDateDay d;
294  d = g_date_get_day(&start);
295 
296  use_eom = ((d - 1) / 7 == 4);
297  }
298  if (use_eom)
299  pt = PERIOD_LAST_WEEKDAY;
300  else pt = PERIOD_NTH_WEEKDAY;
301  }
302  else
303  {
304  if (g_date_is_last_of_month(&start) &&
305  (g_date_get_day(&start) < 31))
306  {
307  // ambiguous, need to examine the checkbox
308  use_eom = gtk_toggle_button_get_active(
309  GTK_TOGGLE_BUTTON(gr->gcb_eom));
310  }
311  else
312  {
313  // if it's the last dom, use eom anyway because it's the 31st.
314  use_eom = g_date_is_last_of_month(&start);
315  }
316  if (use_eom)
317  pt = PERIOD_END_OF_MONTH;
318  else pt = PERIOD_MONTH;
319  }
320  break;
321  case GNCR_YEAR:
322  pt = PERIOD_YEAR;
323  break;
324  default:
325  pt = PERIOD_INVALID;
326  }
327 
328  recurrenceSet(&gr->recurrence, mult, pt, &start, WEEKEND_ADJ_NONE);
329  return &gr->recurrence;
330 }
331 
332 
333 static void
334 gnc_recurrence_finalize(GObject *o)
335 {
336  GncRecurrence *gr = GNC_RECURRENCE(o);
337 
338  if (gr)
339  G_OBJECT_CLASS (parent_class)->finalize (o);
340 }
341 
342 
343 static void
344 gnc_recurrence_class_init( GncRecurrenceClass *klass )
345 {
346  GObjectClass *object_class;
347 
348  object_class = G_OBJECT_CLASS (klass);
349  g_signal_new ("changed",
350  G_OBJECT_CLASS_TYPE (object_class),
351  G_SIGNAL_RUN_FIRST,
352  G_STRUCT_OFFSET (GncRecurrenceClass, changed),
353  NULL,
354  NULL,
355  g_cclosure_marshal_VOID__VOID,
356  G_TYPE_NONE,
357  0);
358 
359  parent_class = g_type_class_peek_parent (klass);
360  object_class->finalize = gnc_recurrence_finalize;
361 }
362 
363 
364 GType
365 gnc_recurrence_get_type()
366 {
367  static GType type = 0;
368  if (type == 0)
369  {
370  static GTypeInfo typeinfo =
371  {
372  sizeof(GncRecurrenceClass),
373  NULL, NULL,
374  (GClassInitFunc)gnc_recurrence_class_init,
375  NULL, NULL,
376  sizeof(GncRecurrence),
377  0,
378  (GInstanceInitFunc)gnc_recurrence_init
379  };
380 
381  type = g_type_register_static (GTK_TYPE_VBOX, "GncRecurrence",
382  &typeinfo, 0);
383  }
384  return type;
385 }
386 
387 
388 GtkWidget *
389 gnc_recurrence_new()
390 {
391  GncRecurrence *gr;
392 
393  ENTER(" ");
394  gr = g_object_new(gnc_recurrence_get_type(), NULL);
395  LEAVE(" ");
396  return GTK_WIDGET(gr);
397 }
398 
399 
400 /* TODO: Maybe this stuff should go into another file.
401  *
402  */
404 {
405  GtkScrolledWindow widget;
406 
407  GtkVBox *vbox;
408  GtkHBox *hbox;
409  GtkHButtonBox *hbb;
410  gint num_rec;
411  GtkButton *buttRemove;
412  GtkButton *buttAdd;
413 
414  GList *rlist;
415 };
416 
417 
418 typedef struct
419 {
420  GtkScrolledWindowClass parent_class;
421  void (*changed) (GncRecurrenceComp *gr);
423 
424 
425 typedef enum
426 {
427  GNCRECURRENCECOMP_CHANGED,
428  GNCRC_LAST_SIGNAL
429 } GNCRC_Signals;
430 
431 
432 static void grc_changed(GtkWidget *w, gpointer data)
433 {
434  g_signal_emit_by_name(data, "changed");
435 }
436 
437 
438 static void addRecurrence(GncRecurrenceComp *grc, GncRecurrence *gr)
439 {
440 
441  gtk_box_pack_start(GTK_BOX(grc->vbox), GTK_WIDGET(gr),
442  FALSE, FALSE, 3);
443  g_signal_connect( G_OBJECT(gr), "changed",
444  G_CALLBACK(grc_changed), grc );
445  grc->num_rec++;
446 
447  gtk_widget_set_sensitive(GTK_WIDGET(grc->buttRemove), (grc->num_rec > 1));
448  g_signal_emit_by_name(G_OBJECT(grc), "changed");
449 
450 
451 }
452 
453 
454 static void removeRecurrence(GncRecurrenceComp *grc)
455 {
456  GList *children, *last;
457 
458  grc->num_rec--;
459 
460  children = gtk_container_get_children(GTK_CONTAINER(grc->vbox));
461  last = g_list_last(children);
462  gtk_widget_destroy(GTK_WIDGET(last->data));
463  g_list_free(children);
464  g_signal_emit_by_name(G_OBJECT(grc), "changed");
465 
466 
467  gtk_widget_set_sensitive(GTK_WIDGET(grc->buttRemove), (grc->num_rec > 1));
468 
469 }
470 
471 
472 static void addClicked(GtkButton *b, gpointer data)
473 {
474  GncRecurrenceComp *grc = data;
475  GncRecurrence *gr;
476 
477  gr = GNC_RECURRENCE(gnc_recurrence_new());
478  addRecurrence(grc, gr);
479 }
480 
481 
482 static void removeClicked(GtkButton *b, gpointer data)
483 {
484  GncRecurrenceComp *grc = data;
485 
486  if (grc->num_rec > 1)
487  removeRecurrence(grc);
488 }
489 
490 
491 void
492 gnc_recurrence_comp_set_list(GncRecurrenceComp *grc, const GList *rlist)
493 {
494  const GList *iter;
495 
496  g_return_if_fail(grc);
497 
498  while (grc->num_rec > 0)
499  removeRecurrence(grc);
500 
501  for (iter = rlist; iter; iter = iter->next)
502  {
503  GncRecurrence *gr = GNC_RECURRENCE(gnc_recurrence_new());
504 
505  gnc_recurrence_set(gr, (Recurrence *)iter->data);
506  addRecurrence(grc, gr);
507  }
508 }
509 
510 
511 GList *
512 gnc_recurrence_comp_get_list(GncRecurrenceComp *grc)
513 {
514  GList *rlist = NULL, *children;
515  gint i;
516 
517 
518  children = gtk_container_get_children(GTK_CONTAINER(grc->vbox));
519  for (i = 0; i < g_list_length(children); i++)
520  {
521  GncRecurrence *gr;
522  const Recurrence *r;
523  gr = GNC_RECURRENCE(g_list_nth_data(children, i));
524  r = gnc_recurrence_get(gr);
525  rlist = g_list_append(rlist, (gpointer)r);
526  }
527  g_list_free(children);
528  return rlist;
529 }
530 
531 
532 static void
533 gnc_recurrence_comp_init(GncRecurrenceComp *grc)
534 {
535  GtkWidget *vb;
536 
537  grc->hbb = GTK_HBUTTON_BOX(gtk_hbutton_box_new());
538  grc->vbox = GTK_VBOX(gtk_vbox_new(FALSE, 1));
539  grc->rlist = NULL;
540 
541  grc->buttAdd = GTK_BUTTON(gtk_button_new_from_stock(GTK_STOCK_ADD));
542  g_signal_connect(G_OBJECT(grc->buttAdd), "clicked",
543  G_CALLBACK(addClicked), grc);
544  grc->buttRemove = GTK_BUTTON(gtk_button_new_from_stock(GTK_STOCK_REMOVE));
545  g_signal_connect(G_OBJECT(grc->buttRemove), "clicked",
546  G_CALLBACK(removeClicked), grc);
547 
548  gtk_box_pack_start(GTK_BOX(grc->hbb), GTK_WIDGET(grc->buttAdd),
549  FALSE, FALSE, 3);
550  gtk_box_pack_start(GTK_BOX(grc->hbb), GTK_WIDGET(grc->buttRemove),
551  FALSE, FALSE, 3);
552 
553  vb = gtk_vbox_new(FALSE, 1);
554  gtk_box_pack_start(GTK_BOX(vb), GTK_WIDGET(grc->hbb),
555  FALSE, FALSE, 3);
556  gtk_box_pack_start(GTK_BOX(vb), GTK_WIDGET(grc->vbox),
557  FALSE, FALSE, 3);
558 
559  gtk_scrolled_window_add_with_viewport(
560  GTK_SCROLLED_WINDOW(grc), GTK_WIDGET(vb));
561  gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(grc),
562  GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
563 
564  grc->num_rec = 0;
565  gtk_widget_show_all(GTK_WIDGET(grc));
566  addClicked(NULL, grc);
567 }
568 
569 
570 static void
571 gnc_recurrence_comp_class_init( GncRecurrenceCompClass *klass )
572 {
573  GObjectClass *object_class;
574 
575  object_class = G_OBJECT_CLASS (klass);
576  g_signal_new ("changed",
577  G_OBJECT_CLASS_TYPE (object_class),
578  G_SIGNAL_RUN_FIRST,
579  G_STRUCT_OFFSET (GncRecurrenceCompClass, changed),
580  NULL,
581  NULL,
582  g_cclosure_marshal_VOID__VOID,
583  G_TYPE_NONE,
584  0);
585 
586  //parent_class = g_type_class_peek_parent (klass);
587  //object_class->finalize = gnc_recurrence_finalize;
588 }
589 
590 
591 GType
592 gnc_recurrence_comp_get_type()
593 {
594  static GType type = 0;
595  if (type == 0)
596  {
597  static GTypeInfo typeinfo =
598  {
599  sizeof(GncRecurrenceCompClass),
600  NULL, NULL,
601  (GClassInitFunc)gnc_recurrence_comp_class_init,
602  NULL, NULL,
603  sizeof(GncRecurrenceComp),
604  0,
605  (GInstanceInitFunc)gnc_recurrence_comp_init
606  };
607 
608  type = g_type_register_static (GTK_TYPE_SCROLLED_WINDOW,
609  "GncRecurrenceComp", &typeinfo, 0);
610  }
611  return type;
612 }
613 
614 
615 GtkWidget *
616 gnc_recurrence_comp_new()
617 {
618  GncRecurrenceComp *grc;
619  grc = g_object_new(gnc_recurrence_comp_get_type(), NULL);
620  return GTK_WIDGET(grc);
621 }
622 
623 /* ========================= END OF FILE =========================== */
time64 gnc_time64_get_day_start_gdate(const GDate *date)
#define ENTER(format, args...)
Definition: qoflog.h:261
All type declarations for the whole Gnucash engine.
GDate helper routines.
#define LEAVE(format, args...)
Definition: qoflog.h:271
time64 gnc_time(time64 *tbuf)
get the current local time
gint64 time64
Definition: gnc-date.h:83
const gchar * QofLogModule
Definition: qofid.h:89