File: datamodel.cpp

package info (click to toggle)
portabase 2.0%2Bgit20110117-1
  • links: PTS
  • area: main
  • in suites: wheezy
  • size: 6,692 kB
  • sloc: cpp: 32,047; sh: 2,675; ansic: 2,320; makefile: 343; xml: 20; python: 16; asm: 10
file content (523 lines) | stat: -rw-r--r-- 15,145 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
/*
 * datamodel.cpp
 *
 * (c) 2010-2011 by Jeremy Bowman <jmbowman@alum.mit.edu>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 */

/** @file datamodel.cpp
 * Source file for DataModel
 */

#include <math.h>
#include "database.h"
#include "datamodel.h"
#include "view.h"

/**
 * Constructor.
 *
 * @param parent This object's parent object
 */
DataModel::DataModel(QObject *parent): QAbstractTableModel(parent), db(0),
    currentView(0), rpp(10), currentPage(1)
{

}

/**
 * Get the database currently in use.
 *
 * @return The current database (0 if none)
 */
Database *DataModel::database()
{
    return db;
}

/**
 * Set the database from which data is to be shown.  Called when a file is
 * created, opened, or significantly altered.
 *
 * @param dbase The database now in use.
 */
void DataModel::setDatabase(Database *dbase)
{
    beginResetModel();
    closeView();
    db = dbase;
    if (currentPage > 0) {
        currentPage = 1;
    }
    setView(db->currentView(), false, true);
    endResetModel();
}

/**
 * Get the view currently in use.
 *
 * @return The current view (0 if none)
 */
View *DataModel::view()
{
    return currentView;
}

/**
 * Set the view currently in use.
 *
 * @param name The name of the new view to use
 * @param applyDefaults True if the view's default filter and sorting are to
 *                      be used, false to retain the current settings
 * @param midReset True if this is part of a larger model reset operation
 */
void DataModel::setView(const QString &name, bool applyDefaults, bool midReset)
{
    if (!midReset) {
        beginResetModel();
    }
    closeView();
    currentView = db->getView(name, applyDefaults);
    currentView->prepareData();
    rpp = currentView->getRowsPerPage();
    processPaginationChange(true);
    if (!midReset) {
        endResetModel();
    }
    emit viewLoaded(currentView);
}

/**
 * Save any settings made to the current view, then remove this object's
 * reference to it.
 */
void DataModel::closeView()
{
    if (currentView) {
        saveViewSettings();
        currentView = 0;
    }
    // don't signal a reset; that'll happen when the next view gets opened
}

/**
 * Store in the database any changes made to column widths or the maximum
 * number of records per page.
 */
void DataModel::saveViewSettings()
{
    if (currentView) {
        currentView->saveColWidths();
        currentView->setRowsPerPage(rpp);
    }
}

/**
 * Make the model aware of a change in the number of rows to display on each
 * page.
 *
 * @param rowsPerPage The new number of rows per page
 */
void DataModel::setRowsPerPage(int rowsPerPage)
{
    if (rpp == rowsPerPage) {
        return;
    }
    rpp = rowsPerPage;
    processPaginationChange();
}

/**
 * Get the page number of the data to be shown
 *
 * @return The current page number (0 if not using pages)
 */
int DataModel::page()
{
    return currentPage;
}

/**
 * Set the page of data to be shown.
 *
 * @param p The new page number
 */
void DataModel::setPage(int p)
{
    if (p == currentPage) {
        return;
    }
    currentPage = p;
    processPaginationChange();
}

/**
 * Make updates to the database view and signal any listening widgets
 * appropriately when a pagination change occurs.  This could be optimized
 * better, but since the main point of pagination is to show few enough rows
 * to avoid displaying a vertical scrollbar, there probably wouldn't be
 * enough performance gain to be worth it.
 *
 * @param midReset True if this is part of a larger model reset operation
 */
void DataModel::processPaginationChange(bool midReset)
{
    if (currentView == 0) {
        // the table and buttons shouldn't even be visible
        return;
    }
    int totalRows = currentView->totalRowCount();
    int firstRow = 0;
    int pageRows = totalRows;
    if (currentPage > 0) { // pagination in use
        int totalPages = (int)(ceil((double)totalRows/(double)rpp));
        if (currentPage > totalPages) {
            currentPage = (totalPages == 0) ? 1 : totalPages;
        }
        emit paginationChanged(currentPage, totalPages);
        firstRow = (currentPage - 1) * rpp;
        pageRows = qMin(rpp, totalRows - firstRow);
    }
    if (!midReset) {
        beginResetModel();
    }
    currentView->setPagination(firstRow, pageRows);
    if (!midReset) {
        endResetModel();
    }
}

/**
 * Set the sorting to use and update the displayed data accordingly.
 *
 * @param name The name of the sorting to use
 */
void DataModel::setSorting(const QString &name)
{
    beginResetModel();
    currentView->sort(name);
    currentView->prepareData();
    endResetModel();
}

/**
 * Update the sorting parameters to indicate the next sorting direction on
 * the specified field.  The order is descending if this field was already
 * being sorted in ascending order, ascending otherwise.
 *
 * @param colIndex The position index of the field to sort on
 */
void DataModel::toggleSort(int colIndex)
{
    beginResetModel();
    currentView->toggleSort(colIndex);
    currentView->prepareData();
    endResetModel();
}

/**
 * Set the filter to use and update the data display accordingly.
 *
 * @param name The name of the filter to use
 */
void DataModel::setFilter(const QString &name)
{
    beginResetModel();
    db->getFilter(name);
    currentView->prepareData();
    processPaginationChange(true);
    endResetModel();
}

/**
 * Force a full refresh of the view data and the display grid.
 */
void DataModel::refresh()
{
    beginResetModel();
    currentView->prepareData();
    processPaginationChange(true);
    endResetModel();
}

/**
 * Update the model to reflect the addition of a new record to the database.
 * If the new row doesn't pass the current filter, there's no need to update
 * the display.
 *
 * @param rowId The ID of the row that was added
 */
void DataModel::addRow(int rowId)
{
    int oldCount = currentView->totalRowCount();
    currentView->prepareData();
    int newCount = currentView->totalRowCount();
    if (newCount > oldCount) {
        int index = currentView->getIndex(rowId);
        int pageStart = qMax(0, (currentPage - 1) * rpp);
        int pageEnd = newCount - 1; // if not paged
        if (currentPage > 0 && pageStart + rpp > newCount) {
            pageEnd = pageStart + rpp - 1; // limit since it is paged
            // anything get pushed to the next page?
            if (index <= pageEnd && newCount > pageEnd + 1) {
                beginRemoveRows(QModelIndex(), pageEnd, pageEnd);
                currentView->setPagination(pageStart + 1, rpp - 1);
                endRemoveRows();
            }
        }
        if (index < pageStart) {
            // was inserted into a previous page
            // include the row from the last page that got pushed here
            beginInsertRows(QModelIndex(), 0, 0);
            currentView->setPagination(pageStart, qMin(rpp, newCount - pageStart));
            endInsertRows();
        }
        else if (index <= pageEnd) {
            // was inserted into this page
            int virtualIndex = index - pageStart;
            beginInsertRows(QModelIndex(), virtualIndex, virtualIndex);
            currentView->setPagination(pageStart, pageEnd - pageStart + 1);
            endInsertRows();
        }
        // no need to signal the view for this, just updating the page buttons
        processPaginationChange(true);
    }

}

/**
 * Update the model to reflect a particular record being edited.
 *
 * @param rowId The ID of the edited row
 * @param oldIndex The index within the view of the row before it was edited
 */
void DataModel::editRow(int rowId, int oldIndex)
{
    currentView->prepareData();
    int newIndex = currentView->getIndex(rowId);
    int pageStart = qMax(0, (currentPage - 1) * rpp);
    int pageEnd = currentView->totalRowCount() - 1; // if not paged
    if (currentPage > 0) {
        pageEnd = pageStart + rpp - 1; // limit if paged
    }
    if (newIndex == oldIndex) {
        // nothing moved, just update that row if it's on the page
        if (newIndex >= pageStart && newIndex <= pageEnd) {
            QModelIndex start = createIndex(newIndex, 0);
            QModelIndex end = createIndex(newIndex, currentView->columnCount() - 1);
            emit dataChanged(start, end);
        }
        return;
    }
    // remove it from its old location
    processRowRemoval(oldIndex, newIndex);
    if (newIndex == -1) {
        // row no longer passes filter, nothing left to do
        return;
    }
    if (newIndex < pageStart) {
        // row got moved to an earlier page, add the previous row to this page
        beginInsertRows(QModelIndex(), 0, 0);
        currentView->setPagination(pageStart, pageEnd - pageStart + 1);
        endInsertRows();
    }
    else if (newIndex <= pageEnd) {
        // got moved elsewhere on the page; add it in
        int virtualIndex = newIndex - pageStart;
        beginInsertRows(QModelIndex(), virtualIndex, virtualIndex);
        currentView->setPagination(pageStart, pageEnd - pageStart + 1);
        endInsertRows();
    }
}

/**
 * Delete the specified record from the database and update the model
 * accordingly.
 *
 * @param rowId the ID of the deleted row
 */
void DataModel::deleteRow(int rowId)
{
    int oldIndex = currentView->getIndex(rowId);
    db->deleteRow(rowId);
    currentView->prepareData();
    processRowRemoval(oldIndex);
}

/**
 * Do the grunge work of removing a row from the model.  Used by deleteRow(),
 * but also by editRow() if the row got resorted or failed the filter.
 *
 * @param oldIndex The previous position of the row being removed (in the
 *                 database view, not the model)
 * @param omit The index of a new row to temporarily ignore, -1 if none
 */
void DataModel::processRowRemoval(int oldIndex, int omit)
{
    int pageStart = qMax(0, (currentPage - 1) * rpp);
    int totalRows = currentView->totalRowCount();
    int pageEnd = totalRows - 1; // if not paged
    if (currentPage > 0) {
        pageEnd = pageStart + rpp - 1; // limit if paged
    }
    int tempPageEnd = qMin(pageEnd, totalRows - 1); // tentative actual end index
    if (currentPage > 0 && oldIndex <= pageEnd && totalRows >= pageEnd + 1) {
        // the number of rows in the page will drop by one briefly
        tempPageEnd--;
    }
    if (oldIndex < pageStart) {
        // removed from a previous page; the first row from this one drops back
        beginRemoveRows(QModelIndex(), 0, 0);
        currentView->setPagination(pageStart, tempPageEnd - pageStart + 1, omit);
        endRemoveRows();
    }
    else if (oldIndex <= pageEnd) {
        // it was on this page, remove it
        int virtualIndex = oldIndex - pageStart;
        beginRemoveRows(QModelIndex(), virtualIndex, virtualIndex);
        currentView->setPagination(pageStart, tempPageEnd - pageStart + 1, omit);
        endRemoveRows();
    }
    if (totalRows > pageEnd && (omit == -1 || omit > pageEnd)) {
        // move forward a row from the next page
        beginInsertRows(QModelIndex(), rpp - 1, rpp - 1);
        currentView->setPagination(pageStart, pageEnd - pageStart + 1);
        endInsertRows();
    }
    if (currentPage > 0) {
        // update the pagination
        int rowsOnLastPage = totalRows % rpp;
        if (rowsOnLastPage == 0) {
            // lost a page; were we on it?
            processPaginationChange(currentPage <= totalRows / rpp);
        }
    }
}

/**
 * Delete all rows included in the current filter.  Do this in both the
 * database and the model.
 */
void DataModel::deleteAllRows()
{
    int totalRows = currentView->totalRowCount();
    if (totalRows == 0) {
        // nothing to delete
        return;
    }
    beginResetModel();
    currentView->deleteAllRows();
    currentView->prepareData();
    processPaginationChange(true);
    endResetModel();
}

/**
 * Toggle the value of the boolean field at the specified location.
 *
 * @param index The location of the field to be toggled
 */
void DataModel::toggleBoolean(const QModelIndex &index)
{
    QString colName = headerData(index.column(), Qt::Horizontal).toString();
    int pageStart = qMax(0, (currentPage - 1) * rpp);
    int rowIndex = pageStart + index.row();
    int rowId = currentView->getId(rowIndex);
    db->toggleBoolean(rowId, colName);
    editRow(rowId, rowIndex);
}

/**
 * Get the number of rows in the currently displayed data set.
 *
 * @param parent The parent item in the data tree; should always be an invalid index
 * @return The number of rows in the subset
 */
int DataModel::rowCount(const QModelIndex &parent) const
{
    if (parent.isValid() || !currentView) {
        return 0;
    }
    return currentView->rowCount(parent);
}

/**
 * Get the number of columns in the currently displayed data set, plus an
 * extra empty column to fill any spare width.
 *
 * @param parent The parent item in the data tree; should always be an invalid index
 * @return The number of columns in the subset plus one
 */
int DataModel::columnCount(const QModelIndex &parent) const
{
    if (parent.isValid() || !currentView) {
        return 0;
    }
    return currentView->columnCount(parent) + 1;
}

/**
 * Get the data stored under the given role for the item referred to by the
 * index.
 *
 * @param index The location of the desired data in the table
 * @param role The role the retrieved data will be used in
 * @return The desired data value
 */
QVariant DataModel::data(const QModelIndex &index, int role) const
{
    if (currentView) {
        return currentView->data(index, role);
    }
    return QVariant();
}

/**
 * Get the data for the given role and section in the header with the
 * specified orientation.  For horizontal headers, the section number
 * corresponds to the column number. Similarly, for vertical headers, the
 * section number corresponds to the row number.
 *
 * @param section The column or row number
 * @param orientation The orientation of the header whose data is wanted
 * @param role The role of the header data to get
 * @return The desired header data
 */
QVariant DataModel::headerData(int section, Qt::Orientation orientation,
                               int role) const
{
    if (currentView) {
        return currentView->headerData(section, orientation, role);
    }
    return QVariant();
}

/**
 * Begin a model reset operation.  Calls QAbstractItemModel::beginResetModel()
 * on Qt 4.6 and above, does nothing on earlier versions where the cruder
 * all-in-one reset() method has to be used.
 */
void DataModel::beginResetModel()
{
#if QT_VERSION >= 0x040600
    QAbstractItemModel::beginResetModel();
#endif
}

/**
 * Complete a model reset operation.  Calls QAbstractItemModel::endResetModel()
 * on Qt 4.6 and above, calls the cruder all-in-one reset() on earlier
 * versions.
 */
void DataModel::endResetModel()
{
#if QT_VERSION >= 0x040600
    QAbstractItemModel::endResetModel();
#else
    reset();
#endif
}