Merge release-5-0 into master
[alexxy/gromacs.git] / src / gromacs / onlinehelp / helpformat.cpp
1 /*
2  * This file is part of the GROMACS molecular simulation package.
3  *
4  * Copyright (c) 2012,2013,2014, by the GROMACS development team, led by
5  * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
6  * and including many others, as listed in the AUTHORS file in the
7  * top-level source directory and at http://www.gromacs.org.
8  *
9  * GROMACS is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU Lesser General Public License
11  * as published by the Free Software Foundation; either version 2.1
12  * of the License, or (at your option) any later version.
13  *
14  * GROMACS 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 GNU
17  * Lesser General Public License for more details.
18  *
19  * You should have received a copy of the GNU Lesser General Public
20  * License along with GROMACS; if not, see
21  * http://www.gnu.org/licenses, or write to the Free Software Foundation,
22  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA.
23  *
24  * If you want to redistribute modifications to GROMACS, please
25  * consider that scientific software is very special. Version
26  * control is crucial - bugs must be traceable. We will be happy to
27  * consider code for inclusion in the official distribution, but
28  * derived work must not be called official GROMACS. Details are found
29  * in the README & COPYING files - if they are missing, get the
30  * official version at http://www.gromacs.org.
31  *
32  * To help us fund GROMACS development, we humbly ask that you cite
33  * the research papers on the package. Check out http://www.gromacs.org.
34  */
35 /*! \internal \file
36  * \brief
37  * Implements functions in helpformat.h.
38  *
39  * \author Teemu Murtola <teemu.murtola@gmail.com>
40  * \ingroup module_onlinehelp
41  */
42 #include "gmxpre.h"
43
44 #include "helpformat.h"
45
46 #include <algorithm>
47 #include <string>
48 #include <vector>
49
50 #include "gromacs/onlinehelp/helpwritercontext.h"
51 #include "gromacs/utility/gmxassert.h"
52 #include "gromacs/utility/stringutil.h"
53
54 namespace gmx
55 {
56
57 /********************************************************************
58  * TextTableFormatter::Impl
59  */
60
61 /*! \internal \brief
62  * Private implementation class for TextTableFormatter.
63  *
64  * \ingroup module_onlinehelp
65  */
66 class TextTableFormatter::Impl
67 {
68     public:
69         /*! \internal \brief
70          * Manages a single column for TextTableFormatter.
71          *
72          * \ingroup module_onlinehelp
73          */
74         struct ColumnData
75         {
76             //! Initializes a text table column with given values.
77             ColumnData(const char *title, int width, bool bWrap)
78                 : title_(title != NULL ? title : ""),
79                   width_(width), bWrap_(bWrap), firstLine_(0),
80                   nextLineIndex_(0), nextLineOffset_(0)
81             {
82                 GMX_ASSERT(width >= 0, "Negative width not possible");
83                 GMX_ASSERT(title_.length() <= static_cast<size_t>(width),
84                            "Title too long for column width");
85             }
86
87             //! Returns the title of the column.
88             const std::string &title() const { return title_; }
89             //! Returns the width of the column.
90             int                width() const { return width_; }
91             /*! \brief
92              * Returns the first line offset for the current row.
93              *
94              * Note that the return value may be outside the printed lines if
95              * there is no text.
96              */
97             int firstLine() const { return firstLine_; }
98
99             /*! \brief
100              * Resets the formatting state.
101              *
102              * After this call, textForNextLine() and hasLinesRemaining() can
103              * be used to format the lines for the column.
104              */
105             void startFormatting()
106             {
107                 nextLineIndex_  = (!lines_.empty() ? -firstLine_ : 0);
108                 nextLineOffset_ = 0;
109             }
110             //! Whether there are lines remaining for textForNextLine().
111             bool hasLinesRemaining() const
112             {
113                 return nextLineIndex_ < static_cast<int>(lines_.size());
114             }
115             /*! \brief
116              * Returns the text for the next line.
117              *
118              * \param[in] columnWidth  Width to wrap the text to.
119              * \returns   Text for the next line, or empty string if there is
120              *   no text for this column.
121              */
122             std::string textForNextLine(int columnWidth)
123             {
124                 if (nextLineIndex_ < 0 || !hasLinesRemaining())
125                 {
126                     ++nextLineIndex_;
127                     return std::string();
128                 }
129                 if (bWrap_)
130                 {
131                     TextLineWrapperSettings  settings;
132                     settings.setLineLength(columnWidth);
133                     TextLineWrapper          wrapper(settings);
134                     const std::string       &currentLine = lines_[nextLineIndex_];
135                     const size_t             prevOffset  = nextLineOffset_;
136                     const size_t             nextOffset
137                         = wrapper.findNextLine(currentLine, prevOffset);
138                     if (nextOffset >= currentLine.size())
139                     {
140                         ++nextLineIndex_;
141                         nextLineOffset_ = 0;
142                     }
143                     else
144                     {
145                         nextLineOffset_ = nextOffset;
146                     }
147                     return wrapper.formatLine(currentLine, prevOffset, nextOffset);
148                 }
149                 else
150                 {
151                     return lines_[nextLineIndex_++];
152                 }
153             }
154
155             //! Statit data: title of the column.
156             std::string                 title_;
157             //! Static data: width of the column.
158             int                         width_;
159             //! Static data: whether to automatically wrap input text.
160             bool                        bWrap_;
161             //! First line offset for the current row.
162             int                         firstLine_;
163             //! Text lines for the current row.
164             std::vector<std::string>    lines_;
165             //! Formatting state: index in `lines_` for the next line.
166             int                         nextLineIndex_;
167             //! Formatting state: offset within line `nextLineIndex_` for the next line.
168             size_t                      nextLineOffset_;
169         };
170
171         //! Container type for column data.
172         typedef std::vector<ColumnData> ColumnList;
173
174         //! Initializes data for an empty formatter.
175         Impl();
176
177         /*! \brief
178          * Convenience method for checked access to data for a column.
179          *
180          * \param[in] index  Zero-based column index.
181          * \returns   \c columns_[index]
182          */
183         ColumnData &columnData(int index)
184         {
185             GMX_ASSERT(index >= 0 && index < static_cast<int>(columns_.size()),
186                        "Invalid column index");
187             return columns_[index];
188         }
189         //! \copydoc columnData()
190         const ColumnData &columnData(int index) const
191         {
192             return const_cast<Impl *>(this)->columnData(index);
193         }
194
195         //! Container for column data.
196         ColumnList              columns_;
197         //! Indentation before the first column.
198         int                     firstColumnIndent_;
199         //! Indentation before the last column if folded.
200         int                     foldLastColumnToNextLineIndent_;
201         //! If true, no output has yet been produced.
202         bool                    bFirstRow_;
203         //! If true, a header will be printed before the first row.
204         bool                    bPrintHeader_;
205 };
206
207 TextTableFormatter::Impl::Impl()
208     : firstColumnIndent_(0), foldLastColumnToNextLineIndent_(-1),
209       bFirstRow_(true), bPrintHeader_(false)
210 {
211 }
212
213 /********************************************************************
214  * TextTableFormatter
215  */
216
217 TextTableFormatter::TextTableFormatter()
218     : impl_(new Impl)
219 {
220 }
221
222 TextTableFormatter::~TextTableFormatter()
223 {
224 }
225
226 void TextTableFormatter::addColumn(const char *title, int width, bool bWrap)
227 {
228     if (title != NULL && title[0] != '\0')
229     {
230         impl_->bPrintHeader_ = true;
231     }
232     impl_->columns_.push_back(Impl::ColumnData(title, width, bWrap));
233 }
234
235 void TextTableFormatter::setFirstColumnIndent(int indent)
236 {
237     GMX_RELEASE_ASSERT(indent >= 0, "Negative indentation not allowed");
238     impl_->firstColumnIndent_ = indent;
239 }
240
241 void TextTableFormatter::setFoldLastColumnToNextLine(int indent)
242 {
243     impl_->foldLastColumnToNextLineIndent_ = indent;
244 }
245
246 bool TextTableFormatter::didOutput() const
247 {
248     return !impl_->bFirstRow_;
249 }
250
251 void TextTableFormatter::clear()
252 {
253     Impl::ColumnList::iterator i;
254     for (i = impl_->columns_.begin(); i != impl_->columns_.end(); ++i)
255     {
256         i->firstLine_ = 0;
257         i->lines_.clear();
258     }
259 }
260
261 void TextTableFormatter::addColumnLine(int index, const std::string &text)
262 {
263     Impl::ColumnData        &column = impl_->columnData(index);
264     TextLineWrapper          wrapper;
265     std::vector<std::string> lines(wrapper.wrapToVector(text));
266     column.lines_.insert(column.lines_.end(), lines.begin(), lines.end());
267 }
268
269 void TextTableFormatter::addColumnHelpTextBlock(
270         int index, const HelpWriterContext &context, const std::string &text)
271 {
272     Impl::ColumnData       &column = impl_->columnData(index);
273     TextLineWrapperSettings settings;
274     // TODO: If in the future, there is actually a coupling between the markup
275     // and the wrapping, this must be postponed into formatRow(), where we do
276     // the actual line wrapping.
277     std::vector<std::string> lines(
278             context.substituteMarkupAndWrapToVector(settings, text));
279     column.lines_.insert(column.lines_.end(), lines.begin(), lines.end());
280 }
281
282 void TextTableFormatter::setColumnFirstLineOffset(int index, int firstLine)
283 {
284     GMX_ASSERT(firstLine >= 0, "Invalid first line");
285     Impl::ColumnData &column = impl_->columnData(index);
286     column.firstLine_ = firstLine;
287 }
288
289 std::string TextTableFormatter::formatRow()
290 {
291     std::string                result;
292     Impl::ColumnList::iterator column;
293     // Print a header if this is the first line.
294     if (impl_->bPrintHeader_ && impl_->bFirstRow_)
295     {
296         size_t totalWidth = 0;
297         result.append(impl_->firstColumnIndent_, ' ');
298         for (column  = impl_->columns_.begin();
299              column != impl_->columns_.end();
300              ++column)
301         {
302             std::string title(column->title());
303             if (column != impl_->columns_.end() - 1)
304             {
305                 title.resize(column->width() + 1, ' ');
306                 totalWidth += title.length();
307             }
308             else
309             {
310                 totalWidth += std::min(column->width(),
311                                        static_cast<int>(title.length() + 13));
312             }
313             result.append(title);
314         }
315         result.append("\n");
316         result.append(impl_->firstColumnIndent_, ' ');
317         result.append(totalWidth, '-');
318         result.append("\n");
319     }
320
321     // Format all the lines, one column at a time.
322     std::vector<std::string> lines;
323     std::vector<std::string> columnLines;
324     int                      currentWidth    = 0;
325     bool                     bFoldLastColumn = false;
326     for (column  = impl_->columns_.begin();
327          column != impl_->columns_.end();
328          ++column)
329     {
330         // Format the column into columnLines.
331         column->startFormatting();
332         columnLines.clear();
333         columnLines.reserve(lines.size());
334         for (size_t line = 0; column->hasLinesRemaining(); ++line)
335         {
336             int columnWidth = column->width();
337             if (line < lines.size())
338             {
339                 const int overflow = static_cast<int>(lines[line].length()) - currentWidth;
340                 if (overflow > 0)
341                 {
342                     if (overflow > columnWidth && column->bWrap_)
343                     {
344                         columnLines.push_back(std::string());
345                         continue;
346                     }
347                     columnWidth -= overflow;
348                 }
349             }
350             columnLines.push_back(column->textForNextLine(columnWidth));
351         }
352         if (column == impl_->columns_.end() - 1
353             && impl_->foldLastColumnToNextLineIndent_ >= 0
354             && columnLines.size() >= lines.size() + column->lines_.size())
355         {
356             bFoldLastColumn = true;
357             currentWidth   += column->width();
358             break;
359         }
360         // Add columnLines into lines.
361         if (lines.size() < columnLines.size())
362         {
363             lines.resize(columnLines.size());
364         }
365         for (size_t line = 0; line < columnLines.size(); ++line)
366         {
367             if (column != impl_->columns_.begin() && !columnLines[line].empty())
368             {
369                 lines[line].append(" ");
370                 if (static_cast<int>(lines[line].length()) < currentWidth)
371                 {
372                     lines[line].resize(currentWidth, ' ');
373                 }
374             }
375             lines[line].append(columnLines[line]);
376         }
377         currentWidth += column->width() + 1;
378     }
379
380     // Construct the result by concatenating all the lines.
381     std::vector<std::string>::const_iterator line;
382     for (line = lines.begin(); line != lines.end(); ++line)
383     {
384         result.append(impl_->firstColumnIndent_, ' ');
385         result.append(*line);
386         result.append("\n");
387     }
388
389     if (bFoldLastColumn)
390     {
391         Impl::ColumnList::reference lastColumn = impl_->columns_.back();
392         const int                   totalIndent
393             = impl_->firstColumnIndent_ + impl_->foldLastColumnToNextLineIndent_;
394         lastColumn.startFormatting();
395         currentWidth -= impl_->foldLastColumnToNextLineIndent_;
396         while (lastColumn.hasLinesRemaining())
397         {
398             result.append(totalIndent, ' ');
399             result.append(lastColumn.textForNextLine(currentWidth));
400             result.append("\n");
401         }
402     }
403
404     impl_->bFirstRow_ = false;
405     clear();
406     return result;
407 }
408
409 } // namespace gmx