1 | /* |
---|
2 | * TSPSG: TSP Solver and Generator |
---|
3 | * Copyright (C) 2007-2010 Lёppa <contacts[at]oleksii[dot]name> |
---|
4 | * |
---|
5 | * $Id: tspmodel.cpp 149 2010-12-20 20:53:45Z 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 | */ |
---|
30 | CTSPModel::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 | */ |
---|
41 | void 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 | */ |
---|
58 | int 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 | */ |
---|
71 | QVariant 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) { |
---|
78 | QFont 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 | */ |
---|
100 | Qt::ItemFlags CTSPModel::flags(const QModelIndex &index) const |
---|
101 | { |
---|
102 | Qt::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 | */ |
---|
117 | QVariant 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 | */ |
---|
135 | bool CTSPModel::loadTask(const QString &fname) |
---|
136 | { |
---|
137 | QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); |
---|
138 | QFile 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 | } |
---|
146 | QDataStream ds(&f); |
---|
147 | ds.setVersion(QDataStream::Qt_4_4); |
---|
148 | quint32 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 | */ |
---|
183 | quint16 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 | */ |
---|
195 | void CTSPModel::randomize() |
---|
196 | { |
---|
197 | int randMin = settings->value("Task/RandMin", DEF_RAND_MIN).toInt(); |
---|
198 | int 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 | */ |
---|
220 | int 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 | */ |
---|
232 | bool CTSPModel::saveTask(const QString &fname) |
---|
233 | { |
---|
234 | QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); |
---|
235 | QFile 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 | } |
---|
242 | QDataStream 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 | */ |
---|
323 | bool 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 | table[index.row()][index.column()] = INFINITY; |
---|
330 | else { |
---|
331 | bool ok; |
---|
332 | double tmp = value.toDouble(&ok); |
---|
333 | if (!ok || tmp < 0) |
---|
334 | return false; |
---|
335 | else { |
---|
336 | table[index.row()][index.column()] = tmp; |
---|
337 | if (settings->value("Task/SymmetricMode", DEF_SYMMETRIC_MODE).toBool()) |
---|
338 | table[index.column()][index.row()] = tmp; |
---|
339 | } |
---|
340 | } |
---|
341 | emit dataChanged(index,index); |
---|
342 | return true; |
---|
343 | } |
---|
344 | return false; |
---|
345 | } |
---|
346 | |
---|
347 | /*! |
---|
348 | * \brief Sets number of cities in the current task to \a n. |
---|
349 | * \param n Number of cities to set to. |
---|
350 | * |
---|
351 | * \sa numCities() |
---|
352 | */ |
---|
353 | void CTSPModel::setNumCities(int n) |
---|
354 | { |
---|
355 | if (n == nCities) |
---|
356 | return; |
---|
357 | emit layoutAboutToBeChanged(); |
---|
358 | table.resize(n); |
---|
359 | for (int k = 0; k < n; k++) { |
---|
360 | table[k].resize(n); |
---|
361 | } |
---|
362 | if (n > nCities) |
---|
363 | for (int k = nCities; k < n; k++) |
---|
364 | table[k][k] = INFINITY; |
---|
365 | nCities = n; |
---|
366 | emit layoutChanged(); |
---|
367 | } |
---|
368 | |
---|
369 | /* Privates **********************************************************/ |
---|
370 | |
---|
371 | inline bool CTSPModel::loadError(QDataStream::Status status) |
---|
372 | { |
---|
373 | QString err; |
---|
374 | if (status == QDataStream::Ok) |
---|
375 | return false; |
---|
376 | else if (status == QDataStream::ReadPastEnd) |
---|
377 | err = tr("Unexpected end of file."); |
---|
378 | else if (status == QDataStream::ReadCorruptData) |
---|
379 | err = tr("Corrupt data read. File possibly corrupted."); |
---|
380 | else |
---|
381 | err = tr("Unknown error."); |
---|
382 | QApplication::restoreOverrideCursor(); |
---|
383 | QApplication::setOverrideCursor(QCursor(Qt::ArrowCursor)); |
---|
384 | QMessageBox::critical(QApplication::activeWindow(), tr("Task Load"), tr("Unable to load task:") + "\n" + err); |
---|
385 | QApplication::restoreOverrideCursor(); |
---|
386 | return true; |
---|
387 | } |
---|
388 | |
---|
389 | bool CTSPModel::loadTSPT(QDataStream *ds) |
---|
390 | { |
---|
391 | // Skipping signature |
---|
392 | ds->skipRawData(sizeof(TSPT)); |
---|
393 | if (loadError(ds->status())) |
---|
394 | return false; |
---|
395 | // File version |
---|
396 | quint8 version; |
---|
397 | *ds >> version; |
---|
398 | if (loadError(ds->status())) |
---|
399 | return false; |
---|
400 | if (version > TSPT_VERSION) { |
---|
401 | QApplication::restoreOverrideCursor(); |
---|
402 | QApplication::setOverrideCursor(QCursor(Qt::ArrowCursor)); |
---|
403 | 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.")); |
---|
404 | QApplication::restoreOverrideCursor(); |
---|
405 | return false; |
---|
406 | } |
---|
407 | // Skipping metadata |
---|
408 | ds->skipRawData(TSPT_META_SIZE); |
---|
409 | if (loadError(ds->status())) |
---|
410 | return false; |
---|
411 | // Number of cities |
---|
412 | quint16 size; |
---|
413 | *ds >> size; |
---|
414 | if (loadError(ds->status())) |
---|
415 | return false; |
---|
416 | if ((size < 3) || (size > MAX_NUM_CITIES)) { |
---|
417 | QApplication::restoreOverrideCursor(); |
---|
418 | QApplication::setOverrideCursor(QCursor(Qt::ArrowCursor)); |
---|
419 | QMessageBox::critical(QApplication::activeWindow(), tr("Task Load"), tr("Unable to load task:") + "\n" + tr("Unexpected data read.\nFile is possibly corrupted.")); |
---|
420 | QApplication::restoreOverrideCursor(); |
---|
421 | return false; |
---|
422 | } |
---|
423 | if (nCities != size) { |
---|
424 | setNumCities(size); |
---|
425 | emit numCitiesChanged(size); |
---|
426 | } |
---|
427 | |
---|
428 | double x; // We need this as double may be float on some platforms and we store double values in file |
---|
429 | // Travel costs |
---|
430 | for (int r = 0; r < size; r++) |
---|
431 | for (int c = 0; c < size; c++) |
---|
432 | if (r != c) { |
---|
433 | *ds >> x; |
---|
434 | table[r][c] = x; |
---|
435 | if (loadError(ds->status())) { |
---|
436 | clear(); |
---|
437 | return false; |
---|
438 | } |
---|
439 | } |
---|
440 | emit dataChanged(index(0,0),index(nCities - 1,nCities - 1)); |
---|
441 | QApplication::restoreOverrideCursor(); |
---|
442 | return true; |
---|
443 | } |
---|
444 | |
---|
445 | bool CTSPModel::loadZKT(QDataStream *ds) |
---|
446 | { |
---|
447 | // Skipping signature |
---|
448 | ds->skipRawData(sizeof(ZKT)); |
---|
449 | if (loadError(ds->status())) |
---|
450 | return false; |
---|
451 | // File version |
---|
452 | quint16 version; |
---|
453 | ds->readRawData(reinterpret_cast<char *>(&version),2); |
---|
454 | if (loadError(ds->status())) |
---|
455 | return false; |
---|
456 | if (version > ZKT_VERSION) { |
---|
457 | QApplication::restoreOverrideCursor(); |
---|
458 | QApplication::setOverrideCursor(QCursor(Qt::ArrowCursor)); |
---|
459 | 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.")); |
---|
460 | QApplication::restoreOverrideCursor(); |
---|
461 | return false; |
---|
462 | } |
---|
463 | // Number of cities |
---|
464 | quint8 size; |
---|
465 | ds->readRawData(reinterpret_cast<char *>(&size),1); |
---|
466 | if (loadError(ds->status())) |
---|
467 | return false; |
---|
468 | if ((size < 3) || (size > 5)) { |
---|
469 | QApplication::restoreOverrideCursor(); |
---|
470 | QApplication::setOverrideCursor(QCursor(Qt::ArrowCursor)); |
---|
471 | QMessageBox::critical(QApplication::activeWindow(), tr("Task Load"), tr("Unable to load task:") + "\n" + tr("Unexpected data read.\nFile is possibly corrupted.")); |
---|
472 | QApplication::restoreOverrideCursor(); |
---|
473 | return false; |
---|
474 | } |
---|
475 | if (nCities != size) { |
---|
476 | setNumCities(size); |
---|
477 | emit numCitiesChanged(size); |
---|
478 | } |
---|
479 | // Travel costs |
---|
480 | double val; |
---|
481 | for (int r = 0; r < 5; r++) |
---|
482 | for (int c = 0; c < 5; c++) |
---|
483 | if ((r != c) && (r < size) && (c < size)) { |
---|
484 | ds->readRawData(reinterpret_cast<char *>(&val),8); |
---|
485 | if (loadError(ds->status())) { |
---|
486 | clear(); |
---|
487 | return false; |
---|
488 | } |
---|
489 | table[r][c] = val; |
---|
490 | } else { |
---|
491 | ds->skipRawData(8); |
---|
492 | if (loadError(ds->status())) { |
---|
493 | clear(); |
---|
494 | return false; |
---|
495 | } |
---|
496 | } |
---|
497 | emit dataChanged(index(0,0),index(nCities - 1,nCities - 1)); |
---|
498 | QApplication::restoreOverrideCursor(); |
---|
499 | return true; |
---|
500 | } |
---|
501 | |
---|
502 | inline double CTSPModel::rand(int min, int max) const |
---|
503 | { |
---|
504 | double r; |
---|
505 | if (settings->value("Task/FractionalRandom", DEF_FRACTIONAL_RANDOM).toBool()) { |
---|
506 | double x = qPow(10, settings->value("Task/FractionalAccuracy", DEF_FRACTIONAL_ACCURACY).toInt()); |
---|
507 | r = (double)qRound((double)qrand() / RAND_MAX * (max - min) * x) / x; |
---|
508 | } else |
---|
509 | r = qRound((double)qrand() / RAND_MAX * (max - min)); |
---|
510 | return min + r; |
---|
511 | } |
---|