DUDS
Distributed Update of Data from Something
PathStringGenerator.cpp
Go to the documentation of this file.
1 /*
2  * This file is part of the DUDS project. It is subject to the BSD-style
3  * license terms in the LICENSE file found in the top-level directory of this
4  * distribution and at https://github.com/jjackowski/duds/blob/master/LICENSE.
5  * No part of DUDS, including this file, may be copied, modified, propagated,
6  * or distributed except according to the terms contained in the LICENSE file.
7  *
8  * Copyright (C) 2020 Jeff Jackowski
9  */
11 #include <duds/general/Errors.hpp>
12 #include <assert.h>
13 
14 namespace duds { namespace ui {
15 
17  const std::string &separator,
18  const std::string &ellips,
19  unsigned int mlen,
20  unsigned int mtitle
21 ) : sep(separator), maxLen(mlen), maxPageLen(mtitle) {
22  ellipsis(ellips);
23 }
24 
25 void PathStringGenerator::ellipsis(const std::string &e) {
26  // must be shorter than maxPageLen
27  if (e.size() >= maxPageLen) {
30  );
31  }
32  ellip = e;
33 }
34 
35 void PathStringGenerator::currentHeader(const std::string &h) {
36  // must be shorter than maxLen
37  std::string::size_type len = h.size() + ellip.size() + postCur.size();
38  if (len >= maxLen) {
41  );
42  }
43  preCur = h;
44 }
45 
46 void PathStringGenerator::currentFooter(const std::string &f) {
47  // must be shorter than maxLen
48  std::string::size_type len = f.size() + ellip.size() + preCur.size();
49  if (len >= maxLen) {
52  );
53  }
54  postCur = f;
55 }
56 
57 void PathStringGenerator::maxLength(unsigned int max) {
58  // must be greater than (length of sep) * maxPages + preCur + postCur
59  if (max != -1) {
60  std::string::size_type len = preCur.size() + postCur.size() + ellip.size();
61  if (maxPages != -1) {
62  len += sep.size() * maxPages;
63  }
64  if ((max <= len) || ((maxPageLen != -1) && (max <= maxPageLen))) {
67  StringLength(len)
68  );
69  }
70  }
71  maxLen = max;
72 }
73 
74 void PathStringGenerator::maxTitleLength(unsigned int max) {
75  // must be greater than length of ellip
76  if (max <= ellip.size()) {
78  PathMaxTitleLength(max) << StringLength(ellip.size())
79  );
80  } else if (max > maxLen) {
83  );
84  } else if (max < minPageLen) {
87  );
88  }
89  maxPageLen = max;
90 }
91 
92 void PathStringGenerator::minTitleLength(unsigned int min) {
93  if (min >= maxLen) {
96  );
97  } else if (min > maxPageLen) {
100  );
101  }
102  minPageLen = min;
103 }
104 
105 std::string::size_type PathStringGenerator::titleLength(
106  const std::string &title,
107  int &total,
108  unsigned int max
109 ) const {
110  if (title.size() > max) {
111  if (!abruptSplit) {
112  // search for a space
113  std::string::size_type space =
114  title.find_last_of(' ', max - ellip.size());
115  // check for finding space & not too short
116  if ((space != std::string::npos) && (space >= minPageLen)) {
117  // total increases up to space plus ellipsis
118  total += space + ellip.size();
119  // length of title does not include ellipsis
120  return space;
121  }
122  }
123  // avoid having a space as the last character
124  while ((max > ellip.size()) && (title[max - ellip.size() - 1] == ' ')) {
125  --max;
126  }
127  // total increases by the maximum
128  total += max;
129  // length of title does not include ellipsis
130  return max - ellip.size();
131  }
132  // include whole title
133  total += title.size();
134  return title.size();
135 }
136 
137 void PathStringGenerator::decTitleLen(const TrucatedTitle &tt, int &total) const {
138  total -= tt.len + sep.size();
139  if (tt.len < tt.title->size()) {
140  total -= ellip.size();
141  }
142 }
143 
145  int numtitles,
146  int totalLen,
147  int currLen
148 ) {
149  return (totalLen - currLen) / (numtitles - 1) +
150  (((totalLen - currLen) % (numtitles - 1) > 0) ? 1 : 0);
151 }
152 
153 std::string PathStringGenerator::generate(const Path &path) const {
154  // avoid throwing exception if there is no path
155  if (path.empty()) {
156  return std::string();
157  }
158  // storage for page titles; may need to be altered prior to generating the
159  // final string
160  std::vector<TrucatedTitle> titles;
161  // first page to show; end iterator
162  Path::PageStack::const_reverse_iterator eiter = path.rend();
163  // current page
164  Path::PageStack::const_reverse_iterator citer = path.rcurrent();
165  // start iterator
166  Path::PageStack::const_reverse_iterator iter = citer;
167  // should a page past the current be shown, and does such a page exist?
168  int haveFwd;
169  if (showFwd && (maxPages > 2) && (iter != path.rbegin())) {
170  // start one more page past the current page
171  --iter;
172  haveFwd = 1;
173  } else {
174  haveFwd = 0;
175  }
176  // find the number of pages to show
177  int pcount = eiter - iter;
178  // too many pages?
179  if (pcount > maxPages) {
180  // advance start so maximum pages includes current page
181  eiter -= pcount - maxPages;
182  pcount = maxPages;
183  }
184  // first pass at making title strings
185  int tlen = -(int)sep.size();
186  // current page title length
187  int clen = preCur.size() + postCur.size();
188  titles.reserve(pcount);
189  // Visit pages in reverse order to tabulate titles.
190  // Stop when the total length exceeds about twice the maximum. After this
191  // loop, the results will be shortened to fit.
192  for (; (iter != eiter) && ((tlen < 0) || ((tlen >> 1) < maxLen)); ++iter) {
193  int &len = (iter == citer) ? clen : tlen;
194  // truncate titles to maxium length for single title
195  if (!wholeCurrent || (iter != citer)) {
196  titles.emplace_back(TrucatedTitle(
197  (*iter)->title(),
198  titleLength((*iter)->title(), len, maxPageLen)
199  ));
200  }
201  // truncate current page title to maximum path string length
202  else {
203  titles.emplace_back(TrucatedTitle(
204  (*iter)->title(),
205  titleLength(
206  (*iter)->title(),
207  clen,
208  maxLen - preCur.size() - postCur.size()
209  )
210  ));
211  }
212  // add current item size to total
213  if (iter == citer) {
214  tlen += clen;
215  }
216  // add seperator length
217  tlen += sep.size();
218  }
219  // far too long? only the current page title will fit?
220  if (clen > (maxLen - minPageLen - sep.size())) {
221  // remove all titles before current
222  titles.resize(haveFwd + 1);
223  // title after current?
224  if (haveFwd) {
225  // remove it, too
226  titles.erase(titles.begin());
227  }
228  assert(titles.size() == 1);
229  // reset the length counters
230  tlen = clen = titles.front().len + preCur.size() + postCur.size();
231  assert((unsigned int)tlen <= maxLen);
232  }
233  // too long?
234  else if ((unsigned int)tlen > maxLen) {
235  // reduce number of titles until few are left, or average length is above
236  // the requested minimum length or the string will fit
237  int avgLen = averageLength(titles.size(), tlen, clen);
238  while (
239  // fit check; needed to avoid average check removing extra titles
240  // when some titles are shorter than the minimum
241  ((unsigned int)tlen > maxLen) &&
242  // Average check & remaining titles check.
243  // The remaining titles check avoids removing the current page title.
244  (avgLen >= minPageLen) && (titles.size() > (2 + haveFwd))
245  ) {
246  decTitleLen(titles.back(), tlen);
247  titles.pop_back();
248  avgLen = averageLength(titles.size(), tlen, clen);
249  }
250  // still need some more length and there is a title in the forward direction?
251  if (haveFwd && (titles.size() == 3) && ((unsigned int)tlen > maxLen)) {
252  // remove forward title
253  haveFwd = 0;
254  decTitleLen(titles.front(), tlen);
255  titles.erase(titles.begin());
256  }
257  }
258  // still too long?
259  int overacc = 0; // over length accumulator
260  while ((unsigned int)tlen > maxLen) {
261  // compute a projected maximum length based on available average, and
262  // shorten it by overacc and the a
263  int pmax = averageLength(titles.size(), maxLen, clen) - overacc;
264  int poveracc = overacc;
265  assert(pmax >= minPageLen);
266  // visit stored titles in reverse (forward order when output)
267  std::vector<TrucatedTitle>::reverse_iterator titer = titles.rbegin();
268  std::vector<TrucatedTitle>::reverse_iterator etiter = titles.rend();
269  std::vector<TrucatedTitle>::reverse_iterator ctiter =
270  titles.rend() - (1 + haveFwd);
271  for (; titer != etiter; ++titer) {
272  // skip current page title
273  if (titer == ctiter) continue;
274  // if title, including ellipsis, is more than pmax long . . .
275  std::string::size_type len = titer->len;
276  if (len != titer->title->size()) {
277  len += ellip.size();
278  }
279  if (len > pmax) {
280  // attempt to shorten
281  // remove title from total length
282  decTitleLen(*titer, tlen);
283  int plen = titer->len;
284  titer->len = titleLength(*titer->title, tlen, pmax);
285  tlen += sep.size();
286  // no change or (no space and not the last item other than the current)?
287  if ((plen == titer->len) || // no change, or . . .
288  // length below minimum, or . . .
289  (titer->len < minPageLen) || (
290  // not using abrupt splits; favor splits at spaces
291  !abruptSplit &&
292  // more than two pages, or length below minimum
293  (titles.size() > 2) &&
294  // did not shorten to a space
295  ((*titer->title)[titer->len] != ' ')
296  )
297  ) {
298  // if there is a title in the forward direction and the current
299  // title is either the last or first in the titles vector . . .
300  if (haveFwd && (
301  (titer == titles.rbegin()) ||
302  (titer == (etiter - 1))
303  )) {
304  // Remove the last (forward) title from the path.
305  // The vector is in reverse order.
306  haveFwd = 0;
307  decTitleLen(titles.front(), tlen);
308  titles.erase(titles.begin());
309  overacc = 0;
310  // leave for loop; go back to while loop
311  break;
312  }
313  // if this is the first item of the path . . .
314  else if (titer == titles.rbegin()) {
315  // remove the first title from the path
316  decTitleLen(titles.back(), tlen);
317  titles.pop_back();
318  overacc = 0;
319  // leave for loop; go back to while loop
320  break;
321  } else {
322  // WARNING: no test runs this code block; can it be removed?
323  // leave title as-is
324  if (plen != titer->len) {
325  decTitleLen(*titer, tlen);
326  // revert lengths
327  titer->len = plen;
328  tlen += plen + sep.size();
329  if (titer->len < titer->title->size()) {
330  tlen += ellip.size();
331  }
332  }
333  // alter over length accumulator to account for the length
334  // later
335  overacc += len - pmax;
336  // this was a problem in a test case, but solution prevents
337  // this code from running in that test case, so not sure
338  // problem is solved
339  assert(((int)len - pmax) > 0);
340  }
341  }
342  }
343  }
344  // if still too long, but no attempt to shorten the projected maximum
345  if ((overacc == poveracc) && ((unsigned int)tlen > maxLen)) {
346  // shorten the maximum
347  overacc += std::max(
349  titles.size(),
350  tlen, clen
351  ) * (int)titles.size() - (int)maxLen,
352  1
353  );
354  }
355  }
356  assert((unsigned int)tlen <= maxLen);
357  // build path string
358  std::string res;
359  res.reserve(tlen);
360  std::vector<TrucatedTitle>::reverse_iterator titer = titles.rbegin();
361  std::vector<TrucatedTitle>::reverse_iterator etiter = titles.rend();
362  std::vector<TrucatedTitle>::reverse_iterator ctiter =
363  titles.rend() - (1 + haveFwd);
364  for (; titer != etiter; ++titer) {
365  // at current page title?
366  if (titer == ctiter) {
367  // add header
368  res += preCur;
369  }
370  // put in title
371  res.insert(res.size(), *titer->title, 0, titer->len);
372  // not whole title?
373  if (titer->title->size() != titer->len) {
374  // add ellipsis
375  res += ellip;
376  }
377  // at current page title?
378  if (titer == ctiter) {
379  // add footer
380  res += postCur;
381  }
382  // not at last title?
383  if (titer != (etiter - 1)) {
384  // add separator
385  res += sep;
386  }
387  }
388  assert(res.size() == tlen);
389  return res;
390 }
391 
392 } }
std::string::size_type titleLength(const std::string &title, int &total, unsigned int max) const
Finds the usable length of the given title and adjusts the total length of the path string...
const std::string * title
The title string.
unsigned int maxTitleLength() const
Returns the maximum length allocated to a page title in the output path string.
PageStack::const_reverse_iterator rend() const
Reverse iterator to the start of the page stack.
Definition: Path.hpp:157
std::string preCur
Marker string that preceds the current page title.
unsigned int maxPages
The maximum number of titles to show.
boost::error_info< struct Info_StringLength, std::string::size_type > StringLength
Error attribute used by PathStringGenerator to denote a string length that is important in the contex...
PageStack::const_reverse_iterator rcurrent() const
Reverse iterator to the current page.
Definition: Path.hpp:171
unsigned int maxLen
The maximum length of the output string.
const std::string & ellipsis() const
Returns the string appended to the end of page titles that are shortened to fit.
bool abruptSplit
Break long titles without considering spaces within the title.
static int averageLength(int numtitles, int totalLen, int currLen)
Computes the average length of the titles, excluding the current page title, and rounds up...
unsigned int maxLength() const
Returns the maximum length of the generated path strings.
const std::string & currentHeader() const
Returns the string prepended to the current page title.
bool wholeCurrent
True to show the entire title of the current page if it will fit within maxLen, and use more than max...
std::string ellip
Last character(s) to use when part of a title is not shown.
std::string::size_type len
The length of the title.
boost::error_info< struct Info_PathMaxTitleLength, unsigned int > PathMaxTitleLength
Error attribute used by PathStringGenerator to hold the maximum length of page titles in its output...
bool empty() const
Returns true if the page stack is empty.
Definition: Path.hpp:119
std::string postCur
Marker string that follows the current page title.
unsigned int minPageLen
The minimum length of any title; if fewer characters are availble, fewer titles will be shown...
std::string sep
Separator between page titles.
bool showFwd
True to show one page forward past the current page if such a page exits.
PageStack::const_reverse_iterator rbegin() const
Reverse iterator to the end of the page stack.
Definition: Path.hpp:145
std::string generate(const Path &path) const
Generates the path string for the given Path object.
const std::string & currentFooter() const
Returns the string appended to the end of the current page title.
boost::error_info< struct Info_PathMaxLength, unsigned int > PathMaxLength
Error attribute used by PathStringGenerator to hold the maximum length of its string output...
Error that signifies a PathStringGenerator object was given a parameter value that conflicts with ano...
unsigned int minTitleLength() const
Returns the minimum length for a shortened page title in the output path string.
void decTitleLen(const TrucatedTitle &tt, int &total) const
Removes the given title from the total path length, along with the length of the separator and ellips...
unsigned int maxPageLen
The maximum length of any single title, with the possibile exception of the current page...
boost::error_info< struct Info_PathMinTitleLength, unsigned int > PathMinTitleLength
Error attribute used by PathStringGenerator to hold the minimum length of page titles in its output...
PathStringGenerator()=default
Makes a PathStringGenerator with default values.
General errors.
#define DUDS_THROW_EXCEPTION(x)
Works like BOOST_THROW_EXCEPTION, but includes a stack trace if DUDS_ERRORS_VERBOSE is defined...
Definition: Errors.hpp:48
An internal data structure used to track the titles to include in the path string and the length of e...
Stores a list of pages the user has visited in the order of the visits.
Definition: Path.hpp:23