| 
									
										
										
										
											2018-11-07 17:49:45 +00:00
										 |  |  | #include "FoxLog.hpp"
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-11-12 02:06:44 +00:00
										 |  |  | #include <stdexcept>
 | 
					
						
							|  |  |  | #include <utility>
 | 
					
						
							| 
									
										
										
										
											2018-11-07 23:55:15 +00:00
										 |  |  | #include <QString>
 | 
					
						
							| 
									
										
										
										
											2018-11-07 17:49:45 +00:00
										 |  |  | #include <QDateTime>
 | 
					
						
							|  |  |  | #include <QSqlDatabase>
 | 
					
						
							|  |  |  | #include <QSqlTableModel>
 | 
					
						
							|  |  |  | #include <QSqlRecord>
 | 
					
						
							|  |  |  | #include <QSqlError>
 | 
					
						
							|  |  |  | #include <QSqlQuery>
 | 
					
						
							| 
									
										
										
										
											2018-12-30 12:35:41 +00:00
										 |  |  | #include <QTextStream>
 | 
					
						
							| 
									
										
										
										
											2018-11-07 17:49:45 +00:00
										 |  |  | #include <QDebug>
 | 
					
						
							| 
									
										
										
										
											2018-12-30 12:35:41 +00:00
										 |  |  | #include "Configuration.hpp"
 | 
					
						
							| 
									
										
										
										
											2018-11-12 02:06:44 +00:00
										 |  |  | #include "qt_db_helpers.hpp"
 | 
					
						
							| 
									
										
										
										
											2018-11-07 17:49:45 +00:00
										 |  |  | #include "pimpl_impl.hpp"
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class FoxLog::impl final | 
					
						
							|  |  |  |   : public QSqlTableModel | 
					
						
							|  |  |  | { | 
					
						
							| 
									
										
										
										
											2019-06-06 12:56:25 +01:00
										 |  |  |   Q_OBJECT | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-11-07 17:49:45 +00:00
										 |  |  | public: | 
					
						
							| 
									
										
										
										
											2018-12-30 12:35:41 +00:00
										 |  |  |   impl (Configuration const * configuration); | 
					
						
							| 
									
										
										
										
											2018-11-12 02:06:44 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-05-03 10:21:50 +01:00
										 |  |  |   QVariant data (QModelIndex const& index, int role) const | 
					
						
							|  |  |  |   { | 
					
						
							|  |  |  |     auto value = QSqlTableModel::data (index, role); | 
					
						
							| 
									
										
										
										
											2019-12-08 18:37:35 +00:00
										 |  |  |     if (index.column () == fieldIndex ("when") && Qt::DisplayRole == role) | 
					
						
							| 
									
										
										
										
											2019-05-03 10:21:50 +01:00
										 |  |  |       { | 
					
						
							| 
									
										
										
										
											2019-12-08 18:37:35 +00:00
										 |  |  |         QLocale locale; | 
					
						
							|  |  |  |         value = locale.toString (QDateTime::fromMSecsSinceEpoch (value.toULongLong () * 1000ull, Qt::UTC), locale.dateFormat (QLocale::ShortFormat) + " hh:mm:ss"); | 
					
						
							| 
									
										
										
										
											2019-05-03 10:21:50 +01:00
										 |  |  |       } | 
					
						
							|  |  |  |     return value; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-12-30 12:35:41 +00:00
										 |  |  |   Configuration const * configuration_; | 
					
						
							| 
									
										
										
										
											2018-11-12 02:06:44 +00:00
										 |  |  |   QSqlQuery mutable dupe_query_; | 
					
						
							| 
									
										
										
										
											2018-12-30 12:35:41 +00:00
										 |  |  |   QSqlQuery mutable export_query_; | 
					
						
							| 
									
										
										
										
											2018-11-07 17:49:45 +00:00
										 |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-06 12:56:25 +01:00
										 |  |  | #include "FoxLog.moc"
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-11-18 16:45:16 +00:00
										 |  |  | namespace  | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |   QString const fox_log_ddl { | 
					
						
							|  |  |  |                              "CREATE %1 TABLE fox_log%2 (" | 
					
						
							|  |  |  |                              "	id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL," | 
					
						
							|  |  |  |                              "	\"when\" DATETIME NOT NULL," | 
					
						
							|  |  |  |                              "	call VARCHAR(20) NOT NULL," | 
					
						
							|  |  |  |                              "	grid VARCHAR(4)," | 
					
						
							|  |  |  |                              "	report_sent VARCHAR(3)," | 
					
						
							|  |  |  |                              "	report_rcvd VARCHAR(3)," | 
					
						
							|  |  |  |                              "	band VARCHAR(6) NOT NULL" | 
					
						
							|  |  |  |                              ")" | 
					
						
							|  |  |  |   }; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-12-30 12:35:41 +00:00
										 |  |  | FoxLog::impl::impl (Configuration const * configuration) | 
					
						
							|  |  |  |   : configuration_ {configuration} | 
					
						
							| 
									
										
										
										
											2018-11-07 17:49:45 +00:00
										 |  |  | { | 
					
						
							|  |  |  |   if (!database ().tables ().contains ("fox_log")) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |       QSqlQuery query; | 
					
						
							| 
									
										
										
										
											2018-11-12 02:06:44 +00:00
										 |  |  |       SQL_error_check (query, static_cast<bool (QSqlQuery::*) (QString const&)> (&QSqlQuery::exec), | 
					
						
							| 
									
										
										
										
											2019-11-18 16:45:16 +00:00
										 |  |  |                        fox_log_ddl.arg ("").arg ("")); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   else | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |       QSqlQuery query; | 
					
						
							|  |  |  |       // query to check if table has a unique constraint
 | 
					
						
							|  |  |  |       SQL_error_check (query, static_cast<bool (QSqlQuery::*) (QString const&)> (&QSqlQuery::exec), | 
					
						
							|  |  |  |                        "SELECT COUNT(*)" | 
					
						
							|  |  |  |                        "       FROM sqlite_master" | 
					
						
							|  |  |  |                        "    WHERE" | 
					
						
							|  |  |  |                        "       type = 'index' AND tbl_name = 'fox_log'"); | 
					
						
							|  |  |  |       query.next (); | 
					
						
							|  |  |  |       if (query.value (0).toInt ()) | 
					
						
							|  |  |  |         { | 
					
						
							|  |  |  |           // update to new schema with no dupe disallowing unique
 | 
					
						
							|  |  |  |           // constraint
 | 
					
						
							|  |  |  |           database ().transaction (); | 
					
						
							|  |  |  |           SQL_error_check (query, static_cast<bool (QSqlQuery::*) (QString const&)> (&QSqlQuery::exec), | 
					
						
							|  |  |  |                            fox_log_ddl.arg ("TEMPORARY").arg ("_backup")); | 
					
						
							|  |  |  |           SQL_error_check (query, static_cast<bool (QSqlQuery::*) (QString const&)> (&QSqlQuery::exec), | 
					
						
							|  |  |  |                            "INSERT INTO fox_log_backup SELECT * from fox_log"); | 
					
						
							|  |  |  |           SQL_error_check (query, static_cast<bool (QSqlQuery::*) (QString const&)> (&QSqlQuery::exec), | 
					
						
							|  |  |  |                            "DROP TABLE fox_log"); | 
					
						
							|  |  |  |           SQL_error_check (query, static_cast<bool (QSqlQuery::*) (QString const&)> (&QSqlQuery::exec), | 
					
						
							|  |  |  |                            fox_log_ddl.arg ("").arg ("")); | 
					
						
							|  |  |  |           SQL_error_check (query, static_cast<bool (QSqlQuery::*) (QString const&)> (&QSqlQuery::exec), | 
					
						
							|  |  |  |                            "INSERT INTO fox_log SELECT * from fox_log_backup"); | 
					
						
							|  |  |  |           SQL_error_check (query, static_cast<bool (QSqlQuery::*) (QString const&)> (&QSqlQuery::exec), | 
					
						
							|  |  |  |                            "DROP TABLE fox_log_backup"); | 
					
						
							|  |  |  |           database ().commit (); | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2018-11-07 17:49:45 +00:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-11-12 02:06:44 +00:00
										 |  |  |   SQL_error_check (dupe_query_, &QSqlQuery::prepare, | 
					
						
							| 
									
										
										
										
											2019-01-21 13:35:18 +00:00
										 |  |  |                    "SELECT " | 
					
						
							|  |  |  |                    "    COUNT(*) " | 
					
						
							|  |  |  |                    "  FROM " | 
					
						
							|  |  |  |                    "    fox_log " | 
					
						
							|  |  |  |                    "  WHERE " | 
					
						
							|  |  |  |                    "    call = :call " | 
					
						
							|  |  |  |                    "    AND band = :band"); | 
					
						
							| 
									
										
										
										
											2018-11-12 02:06:44 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-12-30 12:35:41 +00:00
										 |  |  |   SQL_error_check (export_query_, &QSqlQuery::prepare, | 
					
						
							| 
									
										
										
										
											2019-01-21 13:35:18 +00:00
										 |  |  |                    "SELECT " | 
					
						
							|  |  |  |                    "    band" | 
					
						
							|  |  |  |                    "    , \"when\"" | 
					
						
							|  |  |  |                    "    , call" | 
					
						
							|  |  |  |                    "    , grid" | 
					
						
							|  |  |  |                    "    , report_sent" | 
					
						
							|  |  |  |                    "    , report_rcvd " | 
					
						
							|  |  |  |                    "  FROM " | 
					
						
							|  |  |  |                    "    fox_log " | 
					
						
							|  |  |  |                    "  ORDER BY " | 
					
						
							|  |  |  |                    "    \"when\""); | 
					
						
							| 
									
										
										
										
											2018-12-30 12:35:41 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-12-02 02:30:32 +00:00
										 |  |  |   setEditStrategy (QSqlTableModel::OnFieldChange); | 
					
						
							| 
									
										
										
										
											2018-11-07 17:49:45 +00:00
										 |  |  |   setTable ("fox_log"); | 
					
						
							| 
									
										
										
										
											2018-11-12 04:00:55 +00:00
										 |  |  |   setHeaderData (fieldIndex ("when"), Qt::Horizontal, tr ("Date & Time(UTC)")); | 
					
						
							| 
									
										
										
										
											2018-11-07 17:49:45 +00:00
										 |  |  |   setHeaderData (fieldIndex ("call"), Qt::Horizontal, tr ("Call")); | 
					
						
							|  |  |  |   setHeaderData (fieldIndex ("grid"), Qt::Horizontal, tr ("Grid")); | 
					
						
							|  |  |  |   setHeaderData (fieldIndex ("report_sent"), Qt::Horizontal, tr ("Sent")); | 
					
						
							|  |  |  |   setHeaderData (fieldIndex ("report_rcvd"), Qt::Horizontal, tr ("Rcvd")); | 
					
						
							|  |  |  |   setHeaderData (fieldIndex ("band"), Qt::Horizontal, tr ("Band")); | 
					
						
							| 
									
										
										
										
											2018-12-06 05:41:16 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |   // This descending order by time is important, it makes the view
 | 
					
						
							|  |  |  |   // place the latest row at the top, without this the model/view
 | 
					
						
							|  |  |  |   // interactions are both sluggish and unhelpful.
 | 
					
						
							|  |  |  |   setSort (fieldIndex ("when"), Qt::DescendingOrder); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-11-12 02:06:44 +00:00
										 |  |  |   SQL_error_check (*this, &QSqlTableModel::select); | 
					
						
							| 
									
										
										
										
											2018-11-07 17:49:45 +00:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-12-30 12:35:41 +00:00
										 |  |  | FoxLog::FoxLog (Configuration const * configuration) | 
					
						
							|  |  |  |   : m_ {configuration} | 
					
						
							| 
									
										
										
										
											2018-11-07 17:49:45 +00:00
										 |  |  | { | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | FoxLog::~FoxLog () | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-11-25 22:19:41 +00:00
										 |  |  | QSqlTableModel * FoxLog::model () | 
					
						
							| 
									
										
										
										
											2018-11-07 17:49:45 +00:00
										 |  |  | { | 
					
						
							|  |  |  |   return &*m_; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-11-12 02:06:44 +00:00
										 |  |  | namespace | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |   void set_value_maybe_null (QSqlRecord& record, QString const& name, QString const& value) | 
					
						
							|  |  |  |   { | 
					
						
							|  |  |  |     if (value.size ()) | 
					
						
							|  |  |  |       { | 
					
						
							|  |  |  |         record.setValue (name, value); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     else | 
					
						
							|  |  |  |       { | 
					
						
							|  |  |  |         record.setNull (name); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-11-07 17:49:45 +00:00
										 |  |  | bool FoxLog::add_QSO (QDateTime const& when, QString const& call, QString const& grid | 
					
						
							| 
									
										
										
										
											2018-11-12 18:26:05 +00:00
										 |  |  |                       , QString const& report_sent, QString const& report_received | 
					
						
							| 
									
										
										
										
											2018-11-07 17:49:45 +00:00
										 |  |  |                       , QString const& band) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |   auto record = m_->record (); | 
					
						
							| 
									
										
										
										
											2018-11-23 01:18:39 +00:00
										 |  |  |   if (!when.isNull ()) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |       record.setValue ("when", when.toMSecsSinceEpoch () / 1000); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   else | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |       record.setNull ("when"); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   set_value_maybe_null (record, "call", call); | 
					
						
							| 
									
										
										
										
											2018-11-12 02:06:44 +00:00
										 |  |  |   set_value_maybe_null (record, "grid", grid); | 
					
						
							|  |  |  |   set_value_maybe_null (record, "report_sent", report_sent); | 
					
						
							|  |  |  |   set_value_maybe_null (record, "report_rcvd", report_received); | 
					
						
							| 
									
										
										
										
											2018-11-23 01:18:39 +00:00
										 |  |  |   set_value_maybe_null (record, "band", band); | 
					
						
							| 
									
										
										
										
											2018-12-02 02:30:32 +00:00
										 |  |  |   if (m_->isDirty ()) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |       m_->revert ();            // discard any uncommitted changes
 | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2018-12-06 05:41:16 +00:00
										 |  |  |   m_->setEditStrategy (QSqlTableModel::OnManualSubmit); | 
					
						
							|  |  |  |   ConditionalTransaction transaction {*m_}; | 
					
						
							| 
									
										
										
										
											2018-11-25 22:19:41 +00:00
										 |  |  |   auto ok = m_->insertRecord (-1, record); | 
					
						
							|  |  |  |   if (ok) | 
					
						
							| 
									
										
										
										
											2018-11-07 17:49:45 +00:00
										 |  |  |     { | 
					
						
							| 
									
										
										
										
											2018-12-06 05:41:16 +00:00
										 |  |  |       ok = transaction.submit (false); | 
					
						
							| 
									
										
										
										
											2018-11-07 17:49:45 +00:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2018-12-06 05:41:16 +00:00
										 |  |  |   m_->setEditStrategy (QSqlTableModel::OnFieldChange); | 
					
						
							| 
									
										
										
										
											2018-11-25 22:19:41 +00:00
										 |  |  |   return ok; | 
					
						
							| 
									
										
										
										
											2018-11-07 17:49:45 +00:00
										 |  |  | } | 
					
						
							| 
									
										
										
										
											2018-11-12 02:06:44 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | bool FoxLog::dupe (QString const& call, QString const& band) const | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |   m_->dupe_query_.bindValue (":call", call); | 
					
						
							|  |  |  |   m_->dupe_query_.bindValue (":band", band); | 
					
						
							|  |  |  |   SQL_error_check (m_->dupe_query_, static_cast<bool (QSqlQuery::*) ()> (&QSqlQuery::exec)); | 
					
						
							|  |  |  |   m_->dupe_query_.next (); | 
					
						
							|  |  |  |   return m_->dupe_query_.value (0).toInt (); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void FoxLog::reset () | 
					
						
							|  |  |  | { | 
					
						
							| 
									
										
										
										
											2019-01-21 13:35:18 +00:00
										 |  |  |   // synchronize model
 | 
					
						
							|  |  |  |   while (m_->canFetchMore ()) m_->fetchMore (); | 
					
						
							| 
									
										
										
										
											2018-11-12 04:00:55 +00:00
										 |  |  |   if (m_->rowCount ()) | 
					
						
							|  |  |  |     { | 
					
						
							| 
									
										
										
										
											2018-11-25 22:19:41 +00:00
										 |  |  |       m_->setEditStrategy (QSqlTableModel::OnManualSubmit); | 
					
						
							| 
									
										
										
										
											2018-11-12 04:00:55 +00:00
										 |  |  |       ConditionalTransaction transaction {*m_}; | 
					
						
							|  |  |  |       SQL_error_check (*m_, &QSqlTableModel::removeRows, 0, m_->rowCount (), QModelIndex {}); | 
					
						
							|  |  |  |       transaction.submit (); | 
					
						
							| 
									
										
										
										
											2018-11-25 22:19:41 +00:00
										 |  |  |       m_->select ();            // to refresh views
 | 
					
						
							| 
									
										
										
										
											2018-12-02 02:30:32 +00:00
										 |  |  |       m_->setEditStrategy (QSqlTableModel::OnFieldChange); | 
					
						
							| 
									
										
										
										
											2018-11-12 04:00:55 +00:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2018-11-12 02:06:44 +00:00
										 |  |  | } | 
					
						
							| 
									
										
										
										
											2018-12-30 12:35:41 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | namespace | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |   struct ADIF_field | 
					
						
							|  |  |  |   { | 
					
						
							|  |  |  |     explicit ADIF_field (QString const& name, QString const& value) | 
					
						
							|  |  |  |       : name_ {name} | 
					
						
							|  |  |  |       , value_ {value} | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     QString name_; | 
					
						
							|  |  |  |     QString value_; | 
					
						
							|  |  |  |   }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   QTextStream& operator << (QTextStream& os, ADIF_field const& field) | 
					
						
							|  |  |  |   { | 
					
						
							|  |  |  |     if (field.value_.size ()) | 
					
						
							|  |  |  |       { | 
					
						
							|  |  |  |         os << QString {"<%1:%2>%3 "}.arg (field.name_).arg (field.value_.size ()).arg (field.value_); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     return os; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void FoxLog::export_qsos (QTextStream& out) const | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |   out << "WSJT-X FT8 DXpedition Mode Fox Log\n<eoh>"; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   SQL_error_check (m_->export_query_, static_cast<bool (QSqlQuery::*) ()> (&QSqlQuery::exec)); | 
					
						
							|  |  |  |   auto record = m_->export_query_.record (); | 
					
						
							|  |  |  |   auto band_index = record.indexOf ("band"); | 
					
						
							|  |  |  |   auto when_index = record.indexOf ("when"); | 
					
						
							|  |  |  |   auto call_index = record.indexOf ("call"); | 
					
						
							|  |  |  |   auto grid_index = record.indexOf ("grid"); | 
					
						
							|  |  |  |   auto sent_index = record.indexOf ("report_sent"); | 
					
						
							|  |  |  |   auto rcvd_index = record.indexOf ("report_rcvd"); | 
					
						
							|  |  |  |   while (m_->export_query_.next ()) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |       auto when = QDateTime::fromMSecsSinceEpoch (m_->export_query_.value (when_index).toULongLong () * 1000ull, Qt::UTC); | 
					
						
							|  |  |  |       out << '\n' | 
					
						
							|  |  |  |           << ADIF_field {"band", m_->export_query_.value (band_index).toString ()} | 
					
						
							|  |  |  |           << ADIF_field {"mode", "FT8"} | 
					
						
							|  |  |  |           << ADIF_field {"qso_date", when.toString ("yyyyMMdd")} | 
					
						
							|  |  |  |           << ADIF_field {"time_on", when.toString ("hhmmss")} | 
					
						
							|  |  |  |           << ADIF_field {"call", m_->export_query_.value (call_index).toString ()} | 
					
						
							|  |  |  |           << ADIF_field {"gridsquare", m_->export_query_.value (grid_index).toString ()} | 
					
						
							|  |  |  |           << ADIF_field {"rst_sent", m_->export_query_.value (sent_index).toString ()} | 
					
						
							|  |  |  |           << ADIF_field {"rst_rcvd", m_->export_query_.value (rcvd_index).toString ()} | 
					
						
							|  |  |  |           << ADIF_field {"station_callsign", m_->configuration_->my_callsign ()} | 
					
						
							|  |  |  |           << ADIF_field {"my_gridsquare", m_->configuration_->my_grid ()} | 
					
						
							|  |  |  |           << ADIF_field {"operator", m_->configuration_->opCall ()} | 
					
						
							|  |  |  |           << "<eor>"; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   out << endl; | 
					
						
							|  |  |  | } |