YATiSh
Yet Another TIme SHeet
yatishPDF.cpp
Go to the documentation of this file.
1 /********************************************************************
2  * Name: yatishDB.cpp
3  * Purpose: Implements the class exporting PDF from yatish data
4  * Author: Nicolas PĂ©renne (nicolas.perenne@eif-services.eu)
5  * Created: 2021-03-23
6  * Copyright: EIF-services (https://www.eif-services.eu/yatish)
7  * License: GPLv3
8  ********************************************************************/
9 
10 #include "wx_pch.h"
11 #include "yatishPDF.h"
12 
19 yatishPDF::yatishPDF (const Settings& s, const wxListCtrl * l, const yatishDBsqlite& m, const wxString& p)
20  : sttngs (s) {
21  colmax = l->GetColumnCount();
22  rowmax = l->GetItemCount();
23  whereToWrite = p;
24  AliasNbPages();
25  AddPage (wxPORTRAIT, wxPAPER_A4);
26  wxString title;
27  if (colmax == 6) // timeslot view
28  title = _("Timesheet");
29  else // other tables
30  title = _("YATiSh table");
31  SetFont ("Helvetica", "B", 12); // font size: in points
32  double titleWidth = GetStringWidth (title) + 6;
33  SetX ( 0.5*(210 - titleWidth) ); // mm (210 = A4 paper width)
34  SetLineWidth (0.4); // mm
35  Cell (titleWidth, 6, title, wxPDF_BORDER_FRAME, 1, wxPDF_ALIGN_CENTER);
36  Ln ();
37  Summary (m);
38  if (s.pdfCharts) Charts (l);
39  Listing (l);
40 }
41 
44  SaveAsFile (whereToWrite);
45 }
46 
49  Cell ( 30, 4, _("Summary") );
50  SetFont ("Times", "", 10);
51  Cell ( 40, 4, _("Number of records:") );
52  wxString info = wxString::Format ("%d", rowmax);
53  if (rowmax == sttngs.rowLimit) info += _(" (limited)");
54  Cell (30, 4, info, wxPDF_BORDER_NONE, 1);
55  if (colmax != 6) {
56  Cell (30);
57  Cell ( 40, 4, _("Number of fields:") );
58  info.Printf ("%d", colmax);
59  Cell (30, 4, info, wxPDF_BORDER_NONE, 1);
60  }
61  Cell (30);
62  wxString filter = mDB.GetFilter();
63  if ( filter.IsEmpty() ) {
64  Cell ( 40, 4, _("SQL filter:") );
65  SetFont ("Times", "I");
66  Cell (30, 4, _("none"), wxPDF_BORDER_NONE, 1);
67  SetFont ("Times", "");
68  } else {
69  Cell ( 40, 4, _("SQL filter:") );
70  SetFont ("Courier");
71  Cell (0, 4, filter.Trim (false), wxPDF_BORDER_NONE, 1);
72  SetFont ("Times");
73  }
74  if (colmax != 6) return;
75  Cell (30);
76  Cell (40, 4, _("Date range:") );
77  info.Printf ( "[ %s ; %s ]", mDB.GetFirstDay().FormatDate(), mDB.GetLastDay().FormatDate() );
78  Cell (0, 4, info, wxPDF_BORDER_NONE, 1);
79  Cell (30);
80  SetFillColour (*wxYELLOW);
81  Cell (40, 4, _("Total duration (hours):"), wxPDF_BORDER_NONE, 0, wxPDF_ALIGN_LEFT, 1);
82  wxTimeSpan ts;
83  mDB.FilteredTotal (ts);
84  info.Printf ( "%d", ts.GetHours() );
85  Cell (GetStringWidth (info) + 2, 4, info, wxPDF_BORDER_NONE, 1, wxPDF_ALIGN_LEFT, 1);
86 }
87 
89 void yatishPDF::Charts (const wxListCtrl * lst) {
90  if (colmax != 6) return;
91  // data from wxListCtrl: total time (in minutes) for each label
92  wxString timeslot;
93  unsigned long hours, minutes;
94  Map4pie projects, clients, tasks, tools;
95  for (int row = 0; row < rowmax; row++) {
96  timeslot = lst->GetItemText (row, 1);
97  if (row == 0 && timeslot.IsEmpty()) continue; // an activity is underway: don't count it
98  wxStringTokenizer tkz (timeslot, ":");
99  tkz.GetNextToken().ToULong (&hours);
100  tkz.GetNextToken().ToULong (&minutes);
101  minutes += hours*60;
102  projects[lst->GetItemText (row, 2) .ToStdString()] += minutes;
103  clients [lst->GetItemText (row, 3) .ToStdString()] += minutes;
104  tasks [lst->GetItemText (row, 4) .ToStdString()] += minutes;
105  tools [lst->GetItemText (row, 5) .ToStdString()] += minutes;
106  }
107  // no more than 6 labels
108  NoMoreThan6 (projects);
109  NoMoreThan6 (clients);
110  NoMoreThan6 (tasks);
111  NoMoreThan6 (tools);
112  // plots
113  SetFont ("Helvetica", "B", 12);
114  Cell (30, 6, _("Charts"), wxPDF_BORDER_NONE, 1);
115  double x0 = GetX() + 20, y0 = GetY();
116  wxColour colors[] = { // colorbrewer2.org
117  wxColour (127, 201, 127),
118  wxColour (190, 174, 212),
119  wxColour (253, 192, 134),
120  wxColour (255, 255, 153),
121  wxColour ( 56, 108, 176),
122  wxColour (240, 2, 127)
123  };
124  PieChart (x0, y0, 80, 30, projects, colors);
125  PieChart (x0 + 80, y0, 80, 30, clients, colors);
126  PieChart (x0, y0 + 30, 80, 30, tasks, colors);
127  PieChart (x0 + 80, y0 + 30, 80, 30, tools, colors);
128  SetXY (10, y0 + 60); // 10: default margin
129 }
130 
132 void yatishPDF::Listing (const wxListCtrl * lst) {
133  SetFont ("Helvetica", "B", 12);
134  Cell (30, 6, _("Listing"), wxBORDER_NONE, 1);
135  double columnWidth = 20, offset = 0.5*(210 - columnWidth*colmax);
136  SetX (offset);
137  wxListItem item;
138  int col, row;
139  SetFont ("Times", "B", 9);
140  SetLineWidth (0.2); // restore default
141  for (col = 0; col < colmax; col++) {
142  lst->GetColumn (col, item);
143  Cell (columnWidth, 6, item.GetText(), wxPDF_BORDER_FRAME, 0, wxPDF_ALIGN_CENTER);
144  }
145  Ln();
146  SetFont ("Times", "", 6);
147  for (row = 0; row < rowmax; row++) {
148  SetX (offset);
149  for (col = 0; col < colmax; col++) {
150  Cell (columnWidth, 3, lst->GetItemText (row, col), wxPDF_BORDER_LEFT|wxPDF_BORDER_RIGHT);
151  }
152  Ln();
153  }
154  SetX (offset);
155  Cell (columnWidth*colmax, 0, wxEmptyString, wxPDF_BORDER_TOP);
156 }
157 
160  SetFont ("Helvetica", "B", 12);
161  if (sttngs.logo4pdf) {
162  if ( wxFileName::IsFileReadable (sttngs.companyName_or_logoPath) )
163  Image (sttngs.companyName_or_logoPath, 10, 8, 12);
164  else
165  wxLogMessage (_("Logo file not found... thus not drawn.\n"
166  "Check out your PDF settings (Ctrl-S).") );
167  Cell (15, 10, wxEmptyString);
168  } else {
169  Cell (30, 10, sttngs.companyName_or_logoPath, wxPDF_BORDER_BOTTOM);
170  }
171  SetFont ("Helvetica", "I", 12);
172  if (sttngs.motto4pdf)
173  Cell (0, 10, sttngs.companyMotto, wxPDF_BORDER_BOTTOM, 1, wxPDF_ALIGN_RIGHT);
174  else
175  Cell (0, 10, wxDateTime::Now().FormatDate(), wxPDF_BORDER_BOTTOM, 1, wxPDF_ALIGN_RIGHT);
176  Ln (5);
177 }
178 
181  SetY (-15);
182  SetFont ("Helvetica", "", 10);
183  Cell (0, 10, wxString::Format ( "%d/{nb}", PageNo() ),
184  wxPDF_BORDER_NONE, 0, wxPDF_ALIGN_CENTER);
185 }
186 
188 void yatishPDF::PieChart (double xPage, double yPage, double width, double height,
189  const Map4pie& data, const wxColour * colors) {
190  SetFont ("Helvetica", "", 8);
191  double margin = 1;
192  double hLegend = 3;
193  // Determine maximal legend width and sum of data values
194  double sum = 0;
195  double wLegend = 0;
196  double labelWidth;
197  for (auto element : data) {
198  sum = sum + element.second;
199  labelWidth = GetStringWidth (element.first);
200  if (labelWidth > wLegend) wLegend = labelWidth;
201  }
202  double radius = width - 4*margin - hLegend - wLegend;
203  if (radius > height - 2*margin) radius = height - 2*margin;
204  radius = floor (radius/2);
205  double xDiag = xPage + margin + radius;
206  double yDiag = yPage + margin + radius;
207  // Sectors
208  SetLineWidth (0.2);
209  double angle = 0;
210  double angleStart = 0;
211  double angleEnd = 0;
212  int color = 0;
213  for (auto element : data) {
214  angle = (sum != 0) ? floor ( (element.second*360)/sum ) : 0;
215  if (angle != 0) {
216  angleEnd = angleStart + angle;
217  SetFillColour ( colors[color%6] );
218  Sector (xDiag, yDiag, radius, angleStart, angleEnd);
219  angleStart += angle;
220  color++;
221  }
222  }
223  if (angleEnd != 360) {
224  Sector (xDiag, yDiag, radius, angleStart - angle, 360);
225  }
226  // Legends
227  double x1 = xPage + 2*radius + 4*margin;
228  double x2 = x1 + hLegend + margin;
229  double y1 = yDiag - radius + ( 2*radius - data.size()*(hLegend + margin) )/2;
230  color = 0;
231  for (auto element : data) {
232  SetFillColour ( colors[color%6] );
233  Rect (x1, y1, hLegend, hLegend, wxPDF_STYLE_FILLDRAW);
234  SetXY (x2, y1);
235  Cell (wLegend, hLegend, element.first);
236  y1 += hLegend + margin;
237  color++;
238  }
239  }
240 
244  unsigned long mini;
245  string key, others = _("others").ToStdString();
246  while (map.size() > 6) {
247  mini = ULONG_MAX;
248  for (auto element : map) {
249  if (element.first == others) continue;
250  if (element.second < mini) {
251  mini = element.second;
252  key = element.first;
253  }
254  }
255  map[others] += mini;
256  map.erase ( map.find (key) );
257  }
258 }
Interacts with yatish tables in a SQLite database.
wxString GetFilter() const
long FilteredTotal(wxTimeSpan &) const
Returns the total duration of currently viewed timeslots (and their count).
wxDateTime GetFirstDay() const
wxDateTime GetLastDay() const
int colmax
Definition: yatishPDF.h:27
void Footer()
PDF footer (page count).
Definition: yatishPDF.cpp:180
void Listing(const wxListCtrl *)
Writes a table corresponding to the current state of panel #2 (Ctrl-T).
Definition: yatishPDF.cpp:132
int rowmax
Definition: yatishPDF.h:27
~yatishPDF()
Writes the PDF document to the file which was given to the constructor.
Definition: yatishPDF.cpp:43
void Header()
Creates the header of the PDF document, according to user preferences (Ctrl-P).
Definition: yatishPDF.cpp:159
wxString whereToWrite
Definition: yatishPDF.h:28
void NoMoreThan6(Map4pie &)
Modifies its argument map so that there is no more than six records.
Definition: yatishPDF.cpp:243
yatishPDF(const Settings &, const wxListCtrl *, const yatishDBsqlite &, const wxString &="yatish.pdf")
Prints a title then calls other member functions for the body of the PDF document.
Definition: yatishPDF.cpp:19
void Charts(const wxListCtrl *)
Draws charts from the timeslot view only (6 columns).
Definition: yatishPDF.cpp:89
void Summary(const yatishDBsqlite &)
A few pieces of information about the data to be shown by [Charts() and] Listing().
Definition: yatishPDF.cpp:48
void PieChart(double, double, double, double, const Map4pie &, const wxColour *)
Modified from charting.cpp in the wxPdfDocument samples.
Definition: yatishPDF.cpp:188
const Settings & sttngs
Definition: yatishPDF.h:26
As its names implies: the class holding YATiSh settings.
int rowLimit
...to rowLimit lines.
bool pdfCharts
PDF charts?
wxString companyName_or_logoPath
Either the company name or its logo.
bool motto4pdf
Print the company motto in the PDF header?
wxString companyMotto
Slogan or short description of the purpose.
bool logo4pdf
If True the company name is replaced by its logo in the PDF header.
wxColour colors[]
Definition: yatishPlot.cpp:23
map< string, unsigned long > Map4pie
Definition: yatishTypes.h:18