e98f6a63a9e5192c4617b6bc2d3193a8b7251e45
[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 "helpformat.h"
43
44 #include <algorithm>
45 #include <string>
46 #include <vector>
47
48 #include "gromacs/onlinehelp/helpwritercontext.h"
49 #include "gromacs/utility/gmxassert.h"
50 #include "gromacs/utility/stringutil.h"
51
52 namespace gmx
53 {
54
55 /********************************************************************
56  * TextTableFormatter::Impl
57  */
58
59 /*! \internal \brief
60  * Private implementation class for TextTableFormatter.
61  *
62  * \ingroup module_onlinehelp
63  */
64 class TextTableFormatter::Impl
65 {
66     public:
67         /*! \internal \brief
68          * Manages a single column for TextTableFormatter.
69          *
70          * \ingroup module_onlinehelp
71          */
72         struct ColumnData
73         {
74             //! Initializes a text table column with given values.
75             ColumnData(const char *title, int width, bool bWrap)
76                 : title_(title != NULL ? title : ""),
77                   width_(width), bWrap_(bWrap), firstLine_(0),
78                   nextLineIndex_(0), nextLineOffset_(0)
79             {
80                 GMX_ASSERT(width >= 0, "Negative width not possible");
81                 GMX_ASSERT(title_.length() <= static_cast<size_t>(width),
82                            "Title too long for column width");
83             }
84
85             //! Returns the title of the column.
86             const std::string &title() const { return title_; }
87             //! Returns the width of the column.
88             int                width() const { return width_; }
89             /*! \brief
90              * Returns the first line offset for the current row.
91              *
92              * Note that the return value may be outside the printed lines if
93              * there is no text.
94              */
95             int firstLine() const { return firstLine_; }
96
97             /*! \brief
98              * Resets the formatting state.
99              *
100              * After this call, textForNextLine() and hasLinesRemaining() can
101              * be used to format the lines for the column.
102              */
103             void startFormatting()
104             {
105                 nextLineIndex_  = (!lines_.empty() ? -firstLine_ : 0);
106                 nextLineOffset_ = 0;
107             }
108             //! Whether there are lines remaining for textForNextLine().
109             bool hasLinesRemaining() const
110             {
111                 return nextLineIndex_ < static_cast<int>(lines_.size());
112             }
113             /*! \brief
114              * Returns the text for the next line.
115              *
116              * \param[in] columnWidth  Width to wrap the text to.
117              * \returns   Text for the next line, or empty string if there is
118              *   no text for this column.
119              */
120             std::string textForNextLine(int columnWidth)
121             {
122                 if (nextLineIndex_ < 0 || !hasLinesRemaining())
123                 {
124                     ++nextLineIndex_;
125                     return std::string();
126                 }
127                 if (bWrap_)
128                 {
129                     TextLineWrapperSettings  settings;
130                     settings.setLineLength(columnWidth);
131                     TextLineWrapper          wrapper(settings);
132                     const std::string       &currentLine = lines_[nextLineIndex_];
133                     const size_t             prevOffset  = nextLineOffset_;
134                     const size_t             nextOffset
135                         = wrapper.findNextLine(currentLine, prevOffset);
136                     if (nextOffset >= currentLine.size())
137                     {
138                         ++nextLineIndex_;
139                         nextLineOffset_ = 0;
140                     }
141                     else
142                     {
143                         nextLineOffset_ = nextOffset;
144                     }
145                     return wrapper.formatLine(currentLine, prevOffset, nextOffset);
146                 }
147                 else
148                 {
149                     return lines_[nextLineIndex_++];
150                 }
151             }
152
153             //! Statit data: title of the column.
154             std::string                 title_;
155             //! Static data: width of the column.
156             int                         width_;
157             //! Static data: whether to automatically wrap input text.
158             bool                        bWrap_;
159             //! First line offset for the current row.
160             int                         firstLine_;
161             //! Text lines for the current row.
162             std::vector<std::string>    lines_;
163             //! Formatting state: index in `lines_` for the next line.
164             int                         nextLineIndex_;
165             //! Formatting state: offset within line `nextLineIndex_` for the next line.
166             size_t                      nextLineOffset_;
167         };
168
169         //! Container type for column data.
170         typedef std::vector<ColumnData> ColumnList;
171
172         //! Initializes data for an empty formatter.
173         Impl();
174
175         /*! \brief
176          * Convenience method for checked access to data for a column.
177          *
178          * \param[in] index  Zero-based column index.
179          * \returns   \c columns_[index]
180          */
181         ColumnData &columnData(int index)
182         {
183             GMX_ASSERT(index >= 0 && index < static_cast<int>(columns_.size()),
184                        "Invalid column index");
185             return columns_[index];
186         }
187         //! \copydoc columnData()
188         const ColumnData &columnData(int index) const
189         {
190             return const_cast<Impl *>(this)->columnData(index);
191         }
192
193         //! Container for column data.
194         ColumnList              columns_;
195         //! Indentation before the first column.
196         int                     firstColumnIndent_;
197         //! Indentation before the last column if folded.
198         int                     foldLastColumnToNextLineIndent_;
199         //! If true, no output has yet been produced.
200         bool                    bFirstRow_;
201         //! If true, a header will be printed before the first row.
202         bool                    bPrintHeader_;
203 };
204
205 TextTableFormatter::Impl::Impl()
206     : firstColumnIndent_(0), foldLastColumnToNextLineIndent_(-1),
207       bFirstRow_(true), bPrintHeader_(false)
208 {
209 }
210
211 /********************************************************************
212  * TextTableFormatter
213  */
214
215 TextTableFormatter::TextTableFormatter()
216     : impl_(new Impl)
217 {
218 }
219
220 TextTableFormatter::~TextTableFormatter()
221 {
222 }
223
224 void TextTableFormatter::addColumn(const char *title, int width, bool bWrap)
225 {
226     if (title != NULL && title[0] != '\0')
227     {
228         impl_->bPrintHeader_ = true;
229     }
230     impl_->columns_.push_back(Impl::ColumnData(title, width, bWrap));
231 }
232
233 void TextTableFormatter::setFirstColumnIndent(int indent)
234 {
235     GMX_RELEASE_ASSERT(indent >= 0, "Negative indentation not allowed");
236     impl_->firstColumnIndent_ = indent;
237 }
238
239 void TextTableFormatter::setFoldLastColumnToNextLine(int indent)
240 {
241     impl_->foldLastColumnToNextLineIndent_ = indent;
242 }
243
244 bool TextTableFormatter::didOutput() const
245 {
246     return !impl_->bFirstRow_;
247 }
248
249 void TextTableFormatter::clear()
250 {
251     Impl::ColumnList::iterator i;
252     for (i = impl_->columns_.begin(); i != impl_->columns_.end(); ++i)
253     {
254         i->firstLine_ = 0;
255         i->lines_.clear();
256     }
257 }
258
259 void TextTableFormatter::addColumnLine(int index, const std::string &text)
260 {
261     Impl::ColumnData        &column = impl_->columnData(index);
262     TextLineWrapper          wrapper;
263     std::vector<std::string> lines(wrapper.wrapToVector(text));
264     column.lines_.insert(column.lines_.end(), lines.begin(), lines.end());
265 }
266
267 void TextTableFormatter::addColumnHelpTextBlock(
268         int index, const HelpWriterContext &context, const std::string &text)
269 {
270     Impl::ColumnData       &column = impl_->columnData(index);
271     TextLineWrapperSettings settings;
272     // TODO: If in the future, there is actually a coupling between the markup
273     // and the wrapping, this must be postponed into formatRow(), where we do
274     // the actual line wrapping.
275     std::vector<std::string> lines(
276             context.substituteMarkupAndWrapToVector(settings, text));
277     column.lines_.insert(column.lines_.end(), lines.begin(), lines.end());
278 }
279
280 void TextTableFormatter::setColumnFirstLineOffset(int index, int firstLine)
281 {
282     GMX_ASSERT(firstLine >= 0, "Invalid first line");
283     Impl::ColumnData &column = impl_->columnData(index);
284     column.firstLine_ = firstLine;
285 }
286
287 std::string TextTableFormatter::formatRow()
288 {
289     std::string                result;
290     Impl::ColumnList::iterator column;
291     // Print a header if this is the first line.
292     if (impl_->bPrintHeader_ && impl_->bFirstRow_)
293     {
294         size_t totalWidth = 0;
295         result.append(impl_->firstColumnIndent_, ' ');
296         for (column  = impl_->columns_.begin();
297              column != impl_->columns_.end();
298              ++column)
299         {
300             std::string title(column->title());
301             if (column != impl_->columns_.end() - 1)
302             {
303                 title.resize(column->width() + 1, ' ');
304                 totalWidth += title.length();
305             }
306             else
307             {
308                 totalWidth += std::min(column->width(),
309                                        static_cast<int>(title.length() + 13));
310             }
311             result.append(title);
312         }
313         result.append("\n");
314         result.append(impl_->firstColumnIndent_, ' ');
315         result.append(totalWidth, '-');
316         result.append("\n");
317     }
318
319     // Format all the lines, one column at a time.
320     std::vector<std::string> lines;
321     std::vector<std::string> columnLines;
322     int                      currentWidth    = 0;
323     bool                     bFoldLastColumn = false;
324     for (column  = impl_->columns_.begin();
325          column != impl_->columns_.end();
326          ++column)
327     {
328         // Format the column into columnLines.
329         column->startFormatting();
330         columnLines.clear();
331         columnLines.reserve(lines.size());
332         for (size_t line = 0; column->hasLinesRemaining(); ++line)
333         {
334             int columnWidth = column->width();
335             if (line < lines.size())
336             {
337                 const int overflow = static_cast<int>(lines[line].length()) - currentWidth;
338                 if (overflow > 0)
339                 {
340                     if (overflow > columnWidth && column->bWrap_)
341                     {
342                         columnLines.push_back(std::string());
343                         continue;
344                     }
345                     columnWidth -= overflow;
346                 }
347             }
348             columnLines.push_back(column->textForNextLine(columnWidth));
349         }
350         if (column == impl_->columns_.end() - 1
351             && impl_->foldLastColumnToNextLineIndent_ >= 0
352             && columnLines.size() >= lines.size() + column->lines_.size())
353         {
354             bFoldLastColumn = true;
355             currentWidth   += column->width();
356             break;
357         }
358         // Add columnLines into lines.
359         if (lines.size() < columnLines.size())
360         {
361             lines.resize(columnLines.size());
362         }
363         for (size_t line = 0; line < columnLines.size(); ++line)
364         {
365             if (column != impl_->columns_.begin() && !columnLines[line].empty())
366             {
367                 lines[line].append(" ");
368                 if (static_cast<int>(lines[line].length()) < currentWidth)
369                 {
370                     lines[line].resize(currentWidth, ' ');
371                 }
372             }
373             lines[line].append(columnLines[line]);
374         }
375         currentWidth += column->width() + 1;
376     }
377
378     // Construct the result by concatenating all the lines.
379     std::vector<std::string>::const_iterator line;
380     for (line = lines.begin(); line != lines.end(); ++line)
381     {
382         result.append(impl_->firstColumnIndent_, ' ');
383         result.append(*line);
384         result.append("\n");
385     }
386
387     if (bFoldLastColumn)
388     {
389         Impl::ColumnList::reference lastColumn = impl_->columns_.back();
390         const int                   totalIndent
391             = impl_->firstColumnIndent_ + impl_->foldLastColumnToNextLineIndent_;
392         lastColumn.startFormatting();
393         currentWidth -= impl_->foldLastColumnToNextLineIndent_;
394         while (lastColumn.hasLinesRemaining())
395         {
396             result.append(totalIndent, ' ');
397             result.append(lastColumn.textForNextLine(currentWidth));
398             result.append("\n");
399         }
400     }
401
402     impl_->bFirstRow_ = false;
403     clear();
404     return result;
405 }
406
407 } // namespace gmx