source: tspsg-svn/trunk/src/tspmodel.cpp @ 155

Last change on this file since 155 was 155, checked in by laleppa, 14 years ago

Any value that is entered into task table and doesn't contain numbers is now considered an infinity.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id URL
File size: 16.8 KB
Line 
1/*
2 *  TSPSG: TSP Solver and Generator
3 *  Copyright (C) 2007-2011 Lёppa <contacts[at]oleksii[dot]name>
4 *
5 *  $Id: tspmodel.cpp 155 2011-03-16 00:02:49Z laleppa $
6 *  $URL: https://tspsg.svn.sourceforge.net/svnroot/tspsg/trunk/src/tspmodel.cpp $
7 *
8 *  This file is part of TSPSG.
9 *
10 *  TSPSG is free software: you can redistribute it and/or modify
11 *  it under the terms of the GNU General Public License as published by
12 *  the Free Software Foundation, either version 3 of the License, or
13 *  (at your option) any later version.
14 *
15 *  TSPSG is distributed in the hope that it will be useful,
16 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
17 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18 *  GNU General Public License for more details.
19 *
20 *  You should have received a copy of the GNU General Public License
21 *  along with TSPSG.  If not, see <http://www.gnu.org/licenses/>.
22 */
23
24#include "tspmodel.h"
25
26/*!
27 * \brief Class constructor.
28 * \param parent The parent of the table model.
29 */
30CTSPModel::CTSPModel(QObject *parent)
31    : QAbstractTableModel(parent), nCities(0)
32{
33    settings = new QSettings(QSettings::IniFormat, QSettings::UserScope, "TSPSG", "tspsg", this);
34}
35
36/*!
37 * \brief Resets the table, setting all its elements to 0.
38 *
39 * \sa randomize()
40 */
41void CTSPModel::clear()
42{
43    for (int r = 0; r < nCities; r++)
44        for (int c = 0; c < nCities; c++)
45            if (r != c)
46                table[r][c] = 0;
47    emit dataChanged(index(0,0),index(nCities - 1,nCities - 1));
48}
49
50/*!
51 * \brief Returns the column count in the table.
52 * \return Number of columns in the table.
53 *
54 *  Actually, this function returns the number of cities in the current task.
55 *
56 * \sa numCities(), rowCount()
57 */
58int CTSPModel::columnCount(const QModelIndex &) const
59{
60    return nCities;
61}
62
63/*!
64 * \brief Returns the data stored under the given \a role for the item referred to by the \a index.
65 * \param index An item index to get data from.
66 * \param role The role to get data for.
67 * \return Corresponding data.
68 *
69 * \sa setData(), headerData()
70 */
71QVariant CTSPModel::data(const QModelIndex &index, int role) const
72{
73    if (!index.isValid())
74        return QVariant();
75    if (role == Qt::TextAlignmentRole)
76        return int(Qt::AlignCenter);
77    else if (role == Qt::FontRole) {
78QFont font;
79        font.setBold(true);
80        return font;
81    } else if (role == Qt::DisplayRole || role == Qt::EditRole) {
82        if (index.row() < nCities && index.column() < nCities)
83            if (table.at(index.row()).at(index.column()) == INFINITY)
84                return tr(INFSTR);
85            else
86                //! \hack HACK: Converting to string to prevent spinbox in edit mode
87                return QVariant(table.at(index.row()).at(index.column())).toString();
88        else
89            return QVariant();
90    } else if (role == Qt::UserRole)
91        return table[index.row()][index.column()];
92    return QVariant();
93}
94
95/*!
96 * \brief Returns the item flags for the given \a index.
97 * \param index An item index to get flags from.
98 * \return Corresponding item flags.
99 */
100Qt::ItemFlags CTSPModel::flags(const QModelIndex &index) const
101{
102Qt::ItemFlags flags = QAbstractItemModel::flags(index);
103    if (index.row() != index.column())
104        flags |= Qt::ItemIsEditable;
105    return flags;
106}
107
108/*!
109 * \brief Returns the data for the given \a role and \a section in the header with the specified \a orientation.
110 * \param section The section to get header data for.
111 * \param orientation The orientation to get header data for.
112 * \param role The role to get header data for.
113 * \return Corresponding header data.
114 *
115 *  For horizontal headers, the section number corresponds to the column number of items shown beneath it. For vertical headers, the section number typically to the row number of items shown alongside it.
116 */
117QVariant CTSPModel::headerData(int section, Qt::Orientation orientation, int role) const
118{
119    if (role == Qt::DisplayRole) {
120        if (orientation == Qt::Vertical)
121            return tr("City %1").arg(section + 1);
122        else
123            return tr("%1").arg(section + 1);
124    }
125    return QVariant();
126}
127
128/*!
129 * \brief Loads a task from \a fname.
130 * \param fname The name of the file to be loaded.
131 * \return \c true on success, otherwise \c false.
132 *
133 * \sa saveTask()
134 */
135bool CTSPModel::loadTask(const QString &fname)
136{
137    QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
138QFile f(fname);
139    if (!f.open(QIODevice::ReadOnly)) {
140        QApplication::restoreOverrideCursor();
141        QApplication::setOverrideCursor(QCursor(Qt::ArrowCursor));
142        QMessageBox::critical(QApplication::activeWindow(), tr("Task Load"), QString(tr("Unable to open task file.\nError: %1")).arg(f.errorString()));
143        QApplication::restoreOverrideCursor();
144        return false;
145    }
146QDataStream ds(&f);
147    ds.setVersion(QDataStream::Qt_4_4);
148quint32 sig;
149    ds >> sig;
150    if (loadError(ds.status())) {
151        return false;
152    }
153    ds.device()->reset();
154    if (sig == TSPT) {
155        if (!loadTSPT(&ds)) {
156            f.close();
157            return false;
158        }
159    } else if ((sig >> 16) == ZKT) {
160        if (!loadZKT(&ds)) {
161            f.close();
162            return false;
163        }
164    } else {
165        f.close();
166        QApplication::restoreOverrideCursor();
167        QApplication::setOverrideCursor(QCursor(Qt::ArrowCursor));
168        QMessageBox::critical(QApplication::activeWindow(), tr("Task Load"), tr("Unable to load task:") + "\n" + tr("Unknown file format or file is corrupted."));
169        QApplication::restoreOverrideCursor();
170        return false;
171    }
172    f.close();
173    QApplication::restoreOverrideCursor();
174    return true;
175}
176
177/*!
178 * \brief Returns the number of cities.
179 * \return Number of cities in the current task.
180 *
181 * \sa columnCount(), rowCount(), setNumCities()
182 */
183quint16 CTSPModel::numCities() const
184{
185    return nCities;
186}
187
188/*!
189 * \brief Randomizes the table by setting all its values to random ones.
190 *
191 *  Uses TSPSG settings to determine random values range.
192 *
193 * \sa clear()
194 */
195void CTSPModel::randomize()
196{
197int randMin = settings->value("Task/RandMin", DEF_RAND_MIN).toInt();
198int randMax = settings->value("Task/RandMax", DEF_RAND_MAX).toInt();
199    if (settings->value("Task/SymmetricMode", DEF_SYMMETRIC_MODE).toBool()) {
200        for (int r = 0; r < nCities; r++)
201            for (int c = 0; c < r; c++)
202                table[c][r] = table[r][c] = rand(randMin, randMax);
203    } else {
204        for (int r = 0; r < nCities; r++)
205            for (int c = 0; c < nCities; c++)
206                if (r != c)
207                    table[r][c] = rand(randMin, randMax);
208    }
209    emit dataChanged(index(0,0), index(nCities - 1, nCities - 1));
210}
211
212/*!
213 * \brief Returns the row count in the table.
214 * \return Number of rows in the table.
215 *
216 *  Actually, this function returns the number of cities in the current task.
217 *
218 * \sa columnCount(), numCities()
219 */
220int CTSPModel::rowCount(const QModelIndex &) const
221{
222    return nCities;
223}
224
225/*!
226 * \brief Saves current task to \a fname.
227 * \param fname The name of the file to seve to.
228 * \return \c true on success, otherwise \c false.
229 *
230 * \sa loadTask()
231 */
232bool CTSPModel::saveTask(const QString &fname)
233{
234    QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
235QFile f(fname);
236    if (!f.open(QIODevice::WriteOnly)) {
237        QApplication::restoreOverrideCursor();
238        QMessageBox::critical(QApplication::activeWindow(), tr("Task Save"), QString(tr("Unable to create task file.\nError: %1\nMaybe, file is read-only?")).arg(f.errorString()));
239        f.remove();
240        return false;
241    }
242QDataStream ds(&f);
243    ds.setVersion(QDataStream::Qt_4_4);
244    if (f.error() != QFile::NoError) {
245        QApplication::restoreOverrideCursor();
246        QMessageBox::critical(QApplication::activeWindow(), tr("Task Save"), tr("Unable to save task.\nError: %1").arg(f.errorString()));
247        f.close();
248        f.remove();
249        return false;
250    }
251    // File signature
252    ds << TSPT;
253    if (f.error() != QFile::NoError) {
254        QApplication::restoreOverrideCursor();
255        QMessageBox::critical(QApplication::activeWindow(), tr("Task Save"), tr("Unable to save task.\nError: %1").arg(f.errorString()));
256        f.close();
257        f.remove();
258        return false;
259    }
260    // File version
261    ds << TSPT_VERSION;
262    if (f.error() != QFile::NoError) {
263        QApplication::restoreOverrideCursor();
264        QMessageBox::critical(QApplication::activeWindow(), tr("Task Save"), tr("Unable to save task.\nError: %1").arg(f.errorString()));
265        f.close();
266        f.remove();
267        return false;
268    }
269    // File metadata version
270    ds << TSPT_META_VERSION;
271    if (f.error() != QFile::NoError) {
272        QApplication::restoreOverrideCursor();
273        QMessageBox::critical(QApplication::activeWindow(), tr("Task Save"), tr("Unable to save task.\nError: %1").arg(f.errorString()));
274        f.close();
275        f.remove();
276        return false;
277    }
278    // Metadata
279    ds << OSID;
280    if (f.error() != QFile::NoError) {
281        QApplication::restoreOverrideCursor();
282        QMessageBox::critical(QApplication::activeWindow(), tr("Task Save"), tr("Unable to save task.\nError: %1").arg(f.errorString()));
283        f.close();
284        f.remove();
285        return false;
286    }
287    // Number of cities
288    ds << nCities;
289    if (f.error() != QFile::NoError) {
290        QApplication::restoreOverrideCursor();
291        QMessageBox::critical(QApplication::activeWindow(), tr("Task Save"), tr("Unable to save task.\nError: %1").arg(f.errorString()));
292        f.close();
293        f.remove();
294        return false;
295    }
296    // Costs
297    for (int r = 0; r < nCities; r++)
298        for (int c = 0; c < nCities; c++)
299            if (r != c) {
300                ds << static_cast<double>(table[r][c]); // We cast to double because double may be float on some platforms and we store double values in file
301                if (f.error() != QFile::NoError) {
302                    QApplication::restoreOverrideCursor();
303                    QMessageBox::critical(QApplication::activeWindow(), tr("Task Save"), tr("Unable to save task.\nError: %1").arg(f.errorString()));
304                    f.close();
305                    f.remove();
306                    return false;
307                }
308            }
309    f.close();
310    QApplication::restoreOverrideCursor();
311    return true;
312}
313
314/*!
315 * \brief Sets the \a role data for the item at \a index to \a value.
316 * \param index The index of the item to set data at.
317 * \param value The value of the item data to be set.
318 * \param role The role of the item to set data for.
319 * \return \c true on success, otherwise \c false.
320 *
321 * \sa data()
322 */
323bool CTSPModel::setData(const QModelIndex &index, const QVariant &value, int role)
324{
325    if (!index.isValid())
326        return false;
327    if (role == Qt::EditRole && index.row() != index.column()) {
328        if ((value.toString().compare(INFSTR) == 0)
329                || (value.toString().contains(QRegExp("^[^0-9]+$", Qt::CaseInsensitive)))) {
330            table[index.row()][index.column()] = INFINITY;
331        } else {
332bool ok;
333double tmp = value.toDouble(&ok);
334            if (!ok || tmp < 0)
335                return false;
336            else {
337                table[index.row()][index.column()] = tmp;
338                if (settings->value("Task/SymmetricMode", DEF_SYMMETRIC_MODE).toBool())
339                    table[index.column()][index.row()] = tmp;
340            }
341        }
342        emit dataChanged(index,index);
343        return true;
344    }
345    return false;
346}
347
348/*!
349 * \brief Sets number of cities in the current task to \a n.
350 * \param n Number of cities to set to.
351 *
352 * \sa numCities()
353 */
354void CTSPModel::setNumCities(int n)
355{
356    if (n == nCities)
357        return;
358    emit layoutAboutToBeChanged();
359    table.resize(n);
360    for (int k = 0; k < n; k++) {
361        table[k].resize(n);
362    }
363    if (n > nCities)
364        for (int k = nCities; k < n; k++)
365            table[k][k] = INFINITY;
366    nCities = n;
367    emit layoutChanged();
368}
369
370/* Privates **********************************************************/
371
372inline bool CTSPModel::loadError(QDataStream::Status status)
373{
374QString err;
375    if (status == QDataStream::Ok)
376        return false;
377    else if (status == QDataStream::ReadPastEnd)
378        err = tr("Unexpected end of file.");
379    else if (status == QDataStream::ReadCorruptData)
380        err = tr("Corrupt data read. File possibly corrupted.");
381    else
382        err = tr("Unknown error.");
383    QApplication::restoreOverrideCursor();
384    QApplication::setOverrideCursor(QCursor(Qt::ArrowCursor));
385    QMessageBox::critical(QApplication::activeWindow(), tr("Task Load"), tr("Unable to load task:") + "\n" + err);
386    QApplication::restoreOverrideCursor();
387    return true;
388}
389
390bool CTSPModel::loadTSPT(QDataStream *ds)
391{
392    // Skipping signature
393    ds->skipRawData(sizeof(TSPT));
394    if (loadError(ds->status()))
395        return false;
396    // File version
397quint8 version;
398    *ds >> version;
399    if (loadError(ds->status()))
400        return false;
401    if (version > TSPT_VERSION) {
402        QApplication::restoreOverrideCursor();
403        QApplication::setOverrideCursor(QCursor(Qt::ArrowCursor));
404        QMessageBox::critical(QApplication::activeWindow(), tr("Task Load"), tr("Unable to load task:") + "\n" + tr("File version is newer than application supports.\nPlease, try to update application."));
405        QApplication::restoreOverrideCursor();
406        return false;
407    }
408    // Skipping metadata
409    ds->skipRawData(TSPT_META_SIZE);
410    if (loadError(ds->status()))
411        return false;
412    // Number of cities
413quint16 size;
414    *ds >> size;
415    if (loadError(ds->status()))
416        return false;
417    if ((size < 3) || (size > MAX_NUM_CITIES)) {
418        QApplication::restoreOverrideCursor();
419        QApplication::setOverrideCursor(QCursor(Qt::ArrowCursor));
420        QMessageBox::critical(QApplication::activeWindow(), tr("Task Load"), tr("Unable to load task:") + "\n" + tr("Unexpected data read.\nFile is possibly corrupted."));
421        QApplication::restoreOverrideCursor();
422        return false;
423    }
424    if (nCities != size) {
425        setNumCities(size);
426        emit numCitiesChanged(size);
427    }
428
429double x; // We need this as double may be float on some platforms and we store double values in file
430    // Travel costs
431    for (int r = 0; r < size; r++)
432        for (int c = 0; c < size; c++)
433            if (r != c) {
434                *ds >> x;
435                table[r][c] = x;
436                if (loadError(ds->status())) {
437                    clear();
438                    return false;
439                }
440            }
441    emit dataChanged(index(0,0),index(nCities - 1,nCities - 1));
442    QApplication::restoreOverrideCursor();
443    return true;
444}
445
446bool CTSPModel::loadZKT(QDataStream *ds)
447{
448    // Skipping signature
449    ds->skipRawData(sizeof(ZKT));
450    if (loadError(ds->status()))
451        return false;
452    // File version
453quint16 version;
454    ds->readRawData(reinterpret_cast<char *>(&version),2);
455    if (loadError(ds->status()))
456        return false;
457    if (version > ZKT_VERSION) {
458        QApplication::restoreOverrideCursor();
459        QApplication::setOverrideCursor(QCursor(Qt::ArrowCursor));
460        QMessageBox::critical(QApplication::activeWindow(), tr("Task Load"), tr("Unable to load task:") + "\n" + tr("File version is newer than application supports.\nPlease, try to update application."));
461        QApplication::restoreOverrideCursor();
462        return false;
463    }
464    // Number of cities
465quint8 size;
466    ds->readRawData(reinterpret_cast<char *>(&size),1);
467    if (loadError(ds->status()))
468        return false;
469    if ((size < 3) || (size > 5)) {
470        QApplication::restoreOverrideCursor();
471        QApplication::setOverrideCursor(QCursor(Qt::ArrowCursor));
472        QMessageBox::critical(QApplication::activeWindow(), tr("Task Load"), tr("Unable to load task:") + "\n" + tr("Unexpected data read.\nFile is possibly corrupted."));
473        QApplication::restoreOverrideCursor();
474        return false;
475    }
476    if (nCities != size) {
477        setNumCities(size);
478        emit numCitiesChanged(size);
479    }
480    // Travel costs
481double val;
482    for (int r = 0; r < 5; r++)
483        for (int c = 0; c < 5; c++)
484            if ((r != c) && (r < size) && (c < size)) {
485                ds->readRawData(reinterpret_cast<char *>(&val),8);
486                if (loadError(ds->status())) {
487                    clear();
488                    return false;
489                }
490                table[r][c] = val;
491            } else {
492                ds->skipRawData(8);
493                if (loadError(ds->status())) {
494                    clear();
495                    return false;
496                }
497            }
498    emit dataChanged(index(0,0),index(nCities - 1,nCities - 1));
499    QApplication::restoreOverrideCursor();
500    return true;
501}
502
503inline double CTSPModel::rand(int min, int max) const
504{
505double r;
506    if (settings->value("Task/FractionalRandom", DEF_FRACTIONAL_RANDOM).toBool()) {
507double x = qPow(10, settings->value("Task/FractionalAccuracy", DEF_FRACTIONAL_ACCURACY).toInt());
508        r = (double)qRound((double)qrand() / RAND_MAX * (max - min) * x) / x;
509    } else
510        r = qRound((double)qrand() / RAND_MAX * (max - min));
511    return min + r;
512}
Note: See TracBrowser for help on using the repository browser.