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

Last change on this file since 162 was 157, checked in by laleppa, 14 years ago
  • Improved some error messages: now they are more verbose.
  • Handheld version now includes larger icons (48x48 instead of 32x32).
  • Fixed bug #2: Solution graph is too small on high resolution screens.
  • Property svn:eol-style set to native
  • Property svn:keywords set to Id URL
File size: 17.8 KB
RevLine 
[116]1/*
2 *  TSPSG: TSP Solver and Generator
[151]3 *  Copyright (C) 2007-2011 Lёppa <contacts[at]oleksii[dot]name>
[116]4 *
5 *  $Id: tspmodel.cpp 157 2011-03-22 20:31:18Z 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)
[149]31    : QAbstractTableModel(parent), nCities(0)
[116]32{
[149]33    settings = new QSettings(QSettings::IniFormat, QSettings::UserScope, "TSPSG", "tspsg", this);
[116]34}
35
36/*!
37 * \brief Resets the table, setting all its elements to 0.
38 *
39 * \sa randomize()
40 */
41void CTSPModel::clear()
42{
[149]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));
[116]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{
[149]60    return nCities;
[116]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{
[149]73    if (!index.isValid())
74        return QVariant();
75    if (role == Qt::TextAlignmentRole)
76        return int(Qt::AlignCenter);
77    else if (role == Qt::FontRole) {
[116]78QFont font;
[149]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();
[116]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);
[149]103    if (index.row() != index.column())
104        flags |= Qt::ItemIsEditable;
105    return flags;
[116]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{
[149]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();
[116]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{
[149]137    QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
[116]138QFile f(fname);
[149]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    }
[116]146QDataStream ds(&f);
[149]147    ds.setVersion(QDataStream::Qt_4_4);
[116]148quint32 sig;
[149]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;
[116]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{
[149]185    return nCities;
[116]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();
[149]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));
[116]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{
[149]222    return nCities;
[116]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{
[149]234    QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
[116]235QFile f(fname);
[149]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    }
[116]242QDataStream ds(&f);
[149]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;
[116]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{
[149]325    if (!index.isValid())
326        return false;
327    if (role == Qt::EditRole && index.row() != index.column()) {
[155]328        if ((value.toString().compare(INFSTR) == 0)
329                || (value.toString().contains(QRegExp("^[^0-9]+$", Qt::CaseInsensitive)))) {
[149]330            table[index.row()][index.column()] = INFINITY;
[155]331        } else {
[116]332bool ok;
333double tmp = value.toDouble(&ok);
[149]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;
[116]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{
[149]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();
[116]368}
369
370/* Privates **********************************************************/
371
372inline bool CTSPModel::loadError(QDataStream::Status status)
373{
374QString err;
[149]375    if (status == QDataStream::Ok)
376        return false;
377    else if (status == QDataStream::ReadPastEnd)
[157]378        err = tr("Unexpected end of file. The file could be corrupted.");
[149]379    else if (status == QDataStream::ReadCorruptData)
[157]380        err = tr("Corrupt data read. The file could be corrupted.");
[149]381    else
[157]382        err = tr("Unknown error. The file could be corrupted.");
[149]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;
[116]388}
389
390bool CTSPModel::loadTSPT(QDataStream *ds)
391{
[149]392    // Skipping signature
393    ds->skipRawData(sizeof(TSPT));
394    if (loadError(ds->status()))
395        return false;
396    // File version
[116]397quint8 version;
[149]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));
[157]404        QMessageBox::critical(QApplication::activeWindow(), tr("Task Load"), tr("Unable to load task:") + "\n"
405            + tr("File version (%1) is newer than this version of %3 supports (%2).\n"
406                 "Please, try to update the application. Also, the file might be corrupted.")
407                 .arg(version).arg(TSPT_VERSION).arg(QApplication::applicationName()));
[149]408        QApplication::restoreOverrideCursor();
409        return false;
410    }
411    // Skipping metadata
412    ds->skipRawData(TSPT_META_SIZE);
413    if (loadError(ds->status()))
414        return false;
415    // Number of cities
[116]416quint16 size;
[149]417    *ds >> size;
418    if (loadError(ds->status()))
419        return false;
[157]420    if (size < 3) {
[149]421        QApplication::restoreOverrideCursor();
422        QApplication::setOverrideCursor(QCursor(Qt::ArrowCursor));
[157]423        QMessageBox::critical(QApplication::activeWindow(), tr("Task Load"), tr("Unable to load task:") + "\n"
424            + tr("Unexpected data read. The file could be corrupted."));
[149]425        QApplication::restoreOverrideCursor();
426        return false;
427    }
[157]428    if (size > MAX_NUM_CITIES) {
429        QApplication::restoreOverrideCursor();
430        QApplication::setOverrideCursor(QCursor(Qt::ArrowCursor));
431        QMessageBox::critical(QApplication::activeWindow(), tr("Task Load"), tr("Unable to load task:") + "\n"
432            + tr("The task contains more cities (%1) than this version of %3 supports (%2).\n"
433                 "You might be using an old version of the application or the file could be corrupted.")
434                 .arg(size).arg(MAX_NUM_CITIES).arg(QApplication::applicationName()));
435        QApplication::restoreOverrideCursor();
436        return false;
437    }
[149]438    if (nCities != size) {
439        setNumCities(size);
440        emit numCitiesChanged(size);
441    }
[116]442
443double x; // We need this as double may be float on some platforms and we store double values in file
[149]444    // Travel costs
445    for (int r = 0; r < size; r++)
446        for (int c = 0; c < size; c++)
447            if (r != c) {
448                *ds >> x;
449                table[r][c] = x;
450                if (loadError(ds->status())) {
451                    clear();
452                    return false;
453                }
454            }
455    emit dataChanged(index(0,0),index(nCities - 1,nCities - 1));
456    QApplication::restoreOverrideCursor();
457    return true;
[116]458}
459
460bool CTSPModel::loadZKT(QDataStream *ds)
461{
[149]462    // Skipping signature
463    ds->skipRawData(sizeof(ZKT));
464    if (loadError(ds->status()))
465        return false;
466    // File version
[116]467quint16 version;
[149]468    ds->readRawData(reinterpret_cast<char *>(&version),2);
469    if (loadError(ds->status()))
470        return false;
471    if (version > ZKT_VERSION) {
472        QApplication::restoreOverrideCursor();
473        QApplication::setOverrideCursor(QCursor(Qt::ArrowCursor));
[157]474        QMessageBox::critical(QApplication::activeWindow(), tr("Task Load"), tr("Unable to load task:") + "\n"
475            + tr("File version (%1) is newer than this version of %3 supports (%2).\n"
476                 "Please, try to update the application. Also, the file could be corrupted.")
477                 .arg(version).arg(TSPT_VERSION).arg(QApplication::applicationName()));
[149]478        QApplication::restoreOverrideCursor();
479        return false;
480    }
481    // Number of cities
[116]482quint8 size;
[149]483    ds->readRawData(reinterpret_cast<char *>(&size),1);
484    if (loadError(ds->status()))
485        return false;
486    if ((size < 3) || (size > 5)) {
487        QApplication::restoreOverrideCursor();
488        QApplication::setOverrideCursor(QCursor(Qt::ArrowCursor));
[157]489        QMessageBox::critical(QApplication::activeWindow(), tr("Task Load"), tr("Unable to load task:") + "\n"
490            + tr("Unexpected data read. The file could be corrupted."));
[149]491        QApplication::restoreOverrideCursor();
492        return false;
493    }
494    if (nCities != size) {
495        setNumCities(size);
496        emit numCitiesChanged(size);
497    }
498    // Travel costs
[116]499double val;
[149]500    for (int r = 0; r < 5; r++)
501        for (int c = 0; c < 5; c++)
502            if ((r != c) && (r < size) && (c < size)) {
503                ds->readRawData(reinterpret_cast<char *>(&val),8);
504                if (loadError(ds->status())) {
505                    clear();
506                    return false;
507                }
508                table[r][c] = val;
509            } else {
510                ds->skipRawData(8);
511                if (loadError(ds->status())) {
512                    clear();
513                    return false;
514                }
515            }
516    emit dataChanged(index(0,0),index(nCities - 1,nCities - 1));
517    QApplication::restoreOverrideCursor();
518    return true;
[116]519}
520
521inline double CTSPModel::rand(int min, int max) const
522{
523double r;
[149]524    if (settings->value("Task/FractionalRandom", DEF_FRACTIONAL_RANDOM).toBool()) {
[116]525double x = qPow(10, settings->value("Task/FractionalAccuracy", DEF_FRACTIONAL_ACCURACY).toInt());
[149]526        r = (double)qRound((double)qrand() / RAND_MAX * (max - min) * x) / x;
527    } else
528        r = qRound((double)qrand() / RAND_MAX * (max - min));
529    return min + r;
[116]530}
Note: See TracBrowser for help on using the repository browser.