source: tspsg/src/tspmodel.cpp @ 0948070876

appveyorimgbotreadme
Last change on this file since 0948070876 was 2940c14782, checked in by Oleksii Serdiuk, 11 years ago

Relicensed TSP Solver and Generator under GPLv2 license.

Due to potential conflicts between GPLv3 and app stores.

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