17e66c93530ba52051e2aa86390b0706005c8ba4
[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, by the GROMACS development team, led by
5  * David van der Spoel, Berk Hess, Erik Lindahl, and including many
6  * others, as listed in the AUTHORS file in the top-level source
7  * 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         struct ColumnData
68         {
69             ColumnData(const char *title, int width, bool bWrap)
70                 : title_(title != NULL ? title : ""),
71                   width_(width), bWrap_(bWrap), firstLine_(0)
72             {
73                 GMX_ASSERT(width >= 0, "Negative width not possible");
74                 GMX_ASSERT(title_.length() <= static_cast<size_t>(width),
75                            "Title too long for column width");
76             }
77
78             //! Returns the title of the column.
79             const std::string &title() const { return title_; }
80             //! Returns the width of the column.
81             int                width() const { return width_; }
82             /*! \brief
83              * Returns the first line offset for the current row.
84              *
85              * Note that the return value may be outside the printed lines if
86              * there is no text.
87              */
88             int firstLine() const { return firstLine_; }
89             /*! \brief
90              * Returns the index of the last line with text for the current row.
91              *
92              * If there is no text, returns -1.
93              */
94             int lastLine() const
95             {
96                 if (lines_.empty())
97                 {
98                     return -1;
99                 }
100                 return firstLine_ + static_cast<int>(lines_.size()) - 1;
101             }
102             /*! \brief
103              * Returns the text for a line.
104              *
105              * \param[in] line  Zero-based line index.
106              * \returns   Text for line \p line, or empty string if \p line has
107              *     no text for this column.
108              */
109             std::string textForLine(int line) const
110             {
111                 // The second conditional matches if there are no lines
112                 if (line < firstLine() || line > lastLine())
113                 {
114                     return std::string();
115                 }
116                 return lines_[line - firstLine()];
117             }
118
119             //! Statit data: title of the column.
120             std::string                 title_;
121             //! Static data: width of the column.
122             int                         width_;
123             //! Static data: whether to automatically wrap input text.
124             bool                        bWrap_;
125             //! First line offset for the current row.
126             int                         firstLine_;
127             //! Text lines for the current row.
128             std::vector<std::string>    lines_;
129         };
130
131         //! Container type for column data.
132         typedef std::vector<ColumnData> ColumnList;
133
134         //! Initializes data for an empty formatter.
135         Impl();
136
137         /*! \brief
138          * Convenience method for checked access to data for a column.
139          *
140          * \param[in] index  Zero-based column index.
141          * \returns   \c columns_[index]
142          */
143         ColumnData &columnData(int index)
144         {
145             GMX_ASSERT(index >= 0 && index < static_cast<int>(columns_.size()),
146                        "Invalid column index");
147             return columns_[index];
148         }
149         //! \copydoc columnData()
150         const ColumnData &columnData(int index) const
151         {
152             return const_cast<Impl *>(this)->columnData(index);
153         }
154
155         //! Container for column data.
156         ColumnList              columns_;
157         //! Indentation before the first column.
158         int                     firstColumnIndent_;
159         //! If true, no output has yet been produced.
160         bool                    bFirstRow_;
161         //! If true, a header will be printed before the first row.
162         bool                    bPrintHeader_;
163 };
164
165 TextTableFormatter::Impl::Impl()
166     : firstColumnIndent_(0), bFirstRow_(true), bPrintHeader_(false)
167 {
168 }
169
170 /********************************************************************
171  * TextTableFormatter
172  */
173
174 TextTableFormatter::TextTableFormatter()
175     : impl_(new Impl)
176 {
177 }
178
179 TextTableFormatter::~TextTableFormatter()
180 {
181 }
182
183 void TextTableFormatter::addColumn(const char *title, int width, bool bWrap)
184 {
185     if (title != NULL && title[0] != '\0')
186     {
187         impl_->bPrintHeader_ = true;
188     }
189     impl_->columns_.push_back(Impl::ColumnData(title, width, bWrap));
190 }
191
192 void TextTableFormatter::setFirstColumnIndent(int indent)
193 {
194     GMX_RELEASE_ASSERT(indent >= 0, "Negative indentation not allowed");
195     impl_->firstColumnIndent_ = indent;
196 }
197
198 bool TextTableFormatter::didOutput() const
199 {
200     return !impl_->bFirstRow_;
201 }
202
203 void TextTableFormatter::clear()
204 {
205     Impl::ColumnList::iterator i;
206     for (i = impl_->columns_.begin(); i != impl_->columns_.end(); ++i)
207     {
208         i->firstLine_ = 0;
209         i->lines_.clear();
210     }
211 }
212
213 void TextTableFormatter::addColumnLine(int index, const std::string &text)
214 {
215     Impl::ColumnData &column = impl_->columnData(index);
216     TextLineWrapper   wrapper;
217     if (column.bWrap_)
218     {
219         wrapper.settings().setLineLength(column.width());
220     }
221     std::vector<std::string> lines(wrapper.wrapToVector(text));
222     column.lines_.insert(column.lines_.end(), lines.begin(), lines.end());
223 }
224
225 void TextTableFormatter::addColumnHelpTextBlock(
226         int index, const HelpWriterContext &context, const std::string &text)
227 {
228     Impl::ColumnData       &column = impl_->columnData(index);
229     TextLineWrapperSettings settings;
230     if (column.bWrap_)
231     {
232         settings.setLineLength(column.width());
233     }
234     std::vector<std::string> lines(
235             context.substituteMarkupAndWrapToVector(settings, text));
236     column.lines_.insert(column.lines_.end(), lines.begin(), lines.end());
237 }
238
239 void TextTableFormatter::setColumnFirstLineOffset(int index, int firstLine)
240 {
241     GMX_ASSERT(firstLine >= 0, "Invalid first line");
242     Impl::ColumnData &column = impl_->columnData(index);
243     column.firstLine_ = firstLine;
244 }
245
246 int TextTableFormatter::lastColumnLine(int index) const
247 {
248     return impl_->columnData(index).lastLine();
249 }
250
251 std::string TextTableFormatter::formatRow()
252 {
253     std::string result;
254     Impl::ColumnList::const_iterator column;
255     // Print a header if this is the first line.
256     if (impl_->bPrintHeader_ && impl_->bFirstRow_)
257     {
258         size_t totalWidth = 0;
259         result.append(impl_->firstColumnIndent_, ' ');
260         for (column  = impl_->columns_.begin();
261              column != impl_->columns_.end();
262              ++column)
263         {
264             std::string title(column->title());
265             if (column != impl_->columns_.end() - 1)
266             {
267                 title.resize(column->width() + 1, ' ');
268                 totalWidth += title.length();
269             }
270             else
271             {
272                 totalWidth += std::min(column->width(),
273                                        static_cast<int>(title.length() + 13));
274             }
275             result.append(title);
276         }
277         result.append("\n");
278         result.append(impl_->firstColumnIndent_, ' ');
279         result.append(totalWidth, '-');
280         result.append("\n");
281     }
282
283     // Compute the last applicable line.
284     int lastLine = -1;
285     for (column  = impl_->columns_.begin();
286          column != impl_->columns_.end();
287          ++column)
288     {
289         lastLine = std::max(lastLine, column->lastLine());
290     }
291
292     // Format the actual row data.
293     for (int line = 0; line <= lastLine; ++line)
294     {
295         std::string lineResult;
296         size_t      currentWidth = 0;
297         for (column  = impl_->columns_.begin();
298              column != impl_->columns_.end();
299              ++column)
300         {
301             std::string value(column->textForLine(line));
302             if (column != impl_->columns_.begin())
303             {
304                 ++currentWidth;
305                 if (!value.empty())
306                 {
307                     lineResult.append(" ");
308                     if (lineResult.length() < currentWidth)
309                     {
310                         lineResult.resize(currentWidth, ' ');
311                     }
312                 }
313             }
314             // TODO: Rewrap the text if wrapping is on and the previous columns
315             // overflow.
316             lineResult.append(value);
317             currentWidth += column->width();
318         }
319         result.append(impl_->firstColumnIndent_, ' ');
320         result.append(lineResult);
321         result.append("\n");
322     }
323
324     impl_->bFirstRow_ = false;
325     clear();
326     return result;
327 }
328
329 } // namespace gmx