File indexing completed on 2025-01-05 05:09:31

0001 /*
0002     SPDX-FileCopyrightText: 2010-2018 Daniel Nicoletti <dantti12@gmail.com>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "PrinterModel.h"
0008 
0009 #include "kcupslib_log.h"
0010 
0011 #include <QDBusConnection>
0012 #include <QDBusInterface>
0013 #include <QDateTime>
0014 #include <QMimeData>
0015 #include <QPointer>
0016 
0017 #include <KLocalizedString>
0018 #include <KMessageBox>
0019 #include <KUser>
0020 
0021 #include <KCupsRequest.h>
0022 
0023 #include <cups/cups.h>
0024 
0025 const static QStringList attrs(QStringList{KCUPS_PRINTER_NAME,
0026                                            KCUPS_PRINTER_STATE,
0027                                            KCUPS_PRINTER_STATE_MESSAGE,
0028                                            KCUPS_PRINTER_IS_SHARED,
0029                                            KCUPS_PRINTER_IS_ACCEPTING_JOBS,
0030                                            KCUPS_PRINTER_TYPE,
0031                                            KCUPS_PRINTER_LOCATION,
0032                                            KCUPS_PRINTER_INFO,
0033                                            KCUPS_PRINTER_MAKE_AND_MODEL,
0034                                            KCUPS_PRINTER_COMMANDS,
0035                                            KCUPS_MARKER_CHANGE_TIME,
0036                                            KCUPS_MARKER_COLORS,
0037                                            KCUPS_MARKER_LEVELS,
0038                                            KCUPS_MARKER_NAMES,
0039                                            KCUPS_MARKER_TYPES,
0040                                            KCUPS_DEVICE_URI,
0041                                            KCUPS_PRINTER_URI_SUPPORTED,
0042                                            KCUPS_MEMBER_NAMES});
0043 
0044 PrinterModel::PrinterModel(QObject *parent)
0045     : QStandardItemModel(parent)
0046 {
0047     m_roles = QStandardItemModel::roleNames();
0048     m_roles[DestStatus] = "stateMessage";
0049     m_roles[DestName] = "printerName";
0050     m_roles[DestState] = "printerState";
0051     m_roles[DestIsDefault] = "isDefault";
0052     m_roles[DestIsShared] = "isShared";
0053     m_roles[DestIsAcceptingJobs] = "isAcceptingJobs";
0054     m_roles[DestIsPaused] = "isPaused";
0055     m_roles[DestIsClass] = "isClass";
0056     m_roles[DestLocation] = "location";
0057     m_roles[DestDescription] = "info";
0058     m_roles[DestKind] = "kind";
0059     m_roles[DestType] = "type";
0060     m_roles[DestCommands] = "commands";
0061     m_roles[DestMarkerChangeTime] = "markerChangeTime";
0062     m_roles[DestMarkers] = "markers";
0063     m_roles[DestIconName] = "iconName";
0064     m_roles[DestRemote] = "remote";
0065     m_roles[DestUri] = "printerUri";
0066     m_roles[DestUriSupported] = "uriSupported";
0067     m_roles[DestMemberNames] = "memberNames";
0068 
0069     // This is emitted when a printer is added
0070     connect(KCupsConnection::global(), &KCupsConnection::printerAdded, this, &PrinterModel::insertUpdatePrinter);
0071 
0072     // This is emitted when a printer is modified
0073     connect(KCupsConnection::global(), &KCupsConnection::printerModified, this, &PrinterModel::insertUpdatePrinter);
0074 
0075     // This is emitted when a printer has it's state changed
0076     connect(KCupsConnection::global(), &KCupsConnection::printerStateChanged, this, &PrinterModel::insertUpdatePrinter);
0077 
0078     // This is emitted when a printer is stopped
0079     connect(KCupsConnection::global(), &KCupsConnection::printerStopped, this, &PrinterModel::insertUpdatePrinter);
0080 
0081     // This is emitted when a printer is restarted
0082     connect(KCupsConnection::global(), &KCupsConnection::printerRestarted, this, &PrinterModel::insertUpdatePrinter);
0083 
0084     // This is emitted when a printer is shutdown
0085     connect(KCupsConnection::global(), &KCupsConnection::printerShutdown, this, &PrinterModel::insertUpdatePrinter);
0086 
0087     // This is emitted when a printer is removed
0088     connect(KCupsConnection::global(), &KCupsConnection::printerDeleted, this, &PrinterModel::printerRemoved);
0089 
0090     connect(KCupsConnection::global(), &KCupsConnection::serverAudit, this, &PrinterModel::serverChanged);
0091     connect(KCupsConnection::global(), &KCupsConnection::serverStarted, this, &PrinterModel::serverChanged);
0092     connect(KCupsConnection::global(), &KCupsConnection::serverStopped, this, &PrinterModel::serverChanged);
0093     connect(KCupsConnection::global(), &KCupsConnection::serverRestarted, this, &PrinterModel::serverChanged);
0094 
0095     // Deprecated stuff that works better than the above
0096     connect(KCupsConnection::global(), &KCupsConnection::rhPrinterAdded, this, &PrinterModel::insertUpdatePrinterName);
0097     connect(KCupsConnection::global(), &KCupsConnection::rhPrinterRemoved, this, &PrinterModel::printerRemovedName);
0098     connect(KCupsConnection::global(), &KCupsConnection::rhQueueChanged, this, &PrinterModel::insertUpdatePrinterName);
0099 
0100     connect(this, &PrinterModel::rowsInserted, this, &PrinterModel::slotCountChanged);
0101     connect(this, &PrinterModel::rowsRemoved, this, &PrinterModel::slotCountChanged);
0102     connect(this, &PrinterModel::modelReset, this, &PrinterModel::slotCountChanged);
0103 
0104     update();
0105 }
0106 
0107 void PrinterModel::getDestsFinished(KCupsRequest *request)
0108 {
0109     // When there is no printer IPP_NOT_FOUND is returned
0110     if (request->hasError() && request->error() != IPP_NOT_FOUND) {
0111         // clear the model after so that the proper widget can be shown
0112         clear();
0113 
0114         Q_EMIT error(request->error(), request->serverError(), request->errorMsg());
0115         if (request->error() == IPP_SERVICE_UNAVAILABLE && !m_unavailable) {
0116             m_unavailable = true;
0117             Q_EMIT serverUnavailableChanged(m_unavailable);
0118         }
0119     } else {
0120         if (m_unavailable) {
0121             m_unavailable = false;
0122             Q_EMIT serverUnavailableChanged(m_unavailable);
0123         }
0124 
0125         const KCupsPrinters printers = request->printers();
0126         for (int i = 0; i < printers.size(); ++i) {
0127             // If there is a printer and it's not the current one add it
0128             // as a new destination
0129             int dest_row = destRow(printers.at(i).name());
0130             if (dest_row == -1) {
0131                 // not found, insert new one
0132                 insertDest(i, printers.at(i));
0133             } else if (dest_row == i) {
0134                 // update the printer
0135                 updateDest(item(i), printers.at(i));
0136             } else {
0137                 // found at wrong position
0138                 // take it and insert on the right position
0139                 QList<QStandardItem *> row = takeRow(dest_row);
0140                 insertRow(i, row);
0141                 updateDest(item(i), printers.at(i));
0142             }
0143         }
0144 
0145         // remove old printers
0146         // The above code starts from 0 and make sure
0147         // dest == modelIndex(x) and if it's not the
0148         // case it either inserts or moves it.
0149         // so any item > num_jobs can be safely deleted
0150         while (rowCount() > printers.size()) {
0151             removeRow(rowCount() - 1);
0152         }
0153 
0154         setDisplayLocationHint();
0155 
0156         Q_EMIT error(IPP_OK, QString(), QString());
0157     }
0158     request->deleteLater();
0159 }
0160 
0161 void PrinterModel::setDisplayLocationHint()
0162 {
0163     QStringList locList;
0164 
0165     // get location list
0166     for (int i = 0; i < rowCount(); i++) {
0167         const auto val = item(i)->data(DestLocation).toString();
0168         if (!val.isEmpty()) {
0169             locList.append(val);
0170         }
0171     }
0172     // only show the location if there is more than one printer
0173     // and at least two distinct locations exist
0174     locList.removeDuplicates();
0175     m_displayLocationHint = rowCount() > 1 && locList.count() > 1;
0176     Q_EMIT displayLocationHintChanged();
0177 }
0178 
0179 bool PrinterModel::printersOnly() const
0180 {
0181     for (int i = 0; i < rowCount(); i++) {
0182         if (item(i)->data(DestIsClass).toBool()) {
0183             return false;
0184         }
0185     }
0186     return true;
0187 }
0188 
0189 bool PrinterModel::displayLocationHint() const
0190 {
0191     return m_displayLocationHint;
0192 }
0193 
0194 void PrinterModel::slotCountChanged()
0195 {
0196     Q_EMIT countChanged(rowCount());
0197 }
0198 
0199 QVariant PrinterModel::headerData(int section, Qt::Orientation orientation, int role) const
0200 {
0201     if (section == 0 && orientation == Qt::Horizontal && role == Qt::DisplayRole) {
0202         return i18n("Printers");
0203     }
0204     return QVariant();
0205 }
0206 
0207 int PrinterModel::count() const
0208 {
0209     return rowCount();
0210 }
0211 
0212 bool PrinterModel::serverUnavailable() const
0213 {
0214     return m_unavailable;
0215 }
0216 
0217 QHash<int, QByteArray> PrinterModel::roleNames() const
0218 {
0219     return m_roles;
0220 }
0221 
0222 void PrinterModel::pausePrinter(const QString &printerName)
0223 {
0224     QPointer<KCupsRequest> request = new KCupsRequest;
0225     request->pausePrinter(printerName);
0226     request->waitTillFinished();
0227     if (request) {
0228         request->deleteLater();
0229     }
0230 }
0231 
0232 void PrinterModel::resumePrinter(const QString &printerName)
0233 {
0234     QPointer<KCupsRequest> request = new KCupsRequest;
0235     request->resumePrinter(printerName);
0236     request->waitTillFinished();
0237     if (request) {
0238         request->deleteLater();
0239     }
0240 }
0241 
0242 void PrinterModel::rejectJobs(const QString &printerName)
0243 {
0244     QPointer<KCupsRequest> request = new KCupsRequest;
0245     request->rejectJobs(printerName);
0246     request->waitTillFinished();
0247     if (request) {
0248         request->deleteLater();
0249     }
0250 }
0251 
0252 void PrinterModel::acceptJobs(const QString &printerName)
0253 {
0254     QPointer<KCupsRequest> request = new KCupsRequest;
0255     request->acceptJobs(printerName);
0256     request->waitTillFinished();
0257     if (request) {
0258         request->deleteLater();
0259     }
0260 }
0261 
0262 void PrinterModel::update()
0263 {
0264     //                 kcmshell(6331) PrinterModel::update: (QHash(("printer-type", QVariant(int, 75534348) ) ( "marker-names" ,  QVariant(QStringList, ("Cyan",
0265     //                 "Yellow", "Magenta", "Black") ) ) ( "printer-name" ,  QVariant(QString, "EPSON_Stylus_TX105") ) ( "marker-colors" , QVariant(QStringList,
0266     //                 ("#00ffff", "#ffff00", "#ff00ff", "#000000") ) ) ( "printer-location" ,  QVariant(QString, "Luiz Vitor’s MacBook Pro") ) (
0267     //                 "marker-levels" ,  QVariant(QList<int>, ) ) ( "marker-types" ,  QVariant(QStringList, ("inkCartridge", "inkCartridge", "inkCartridge",
0268     //                 "inkCartridge") ) ) ( "printer-is-shared" ,  QVariant(bool, true) ) ( "printer-state-message" ,  QVariant(QString, "") ) (
0269     //                 "printer-commands" ,  QVariant(QStringList, ("Clean", "PrintSelfTestPage", "ReportLevels") ) ) ( "marker-change-time" ,  QVariant(int,
0270     //                 1267903160) ) ( "printer-state" ,  QVariant(int, 3) ) ( "printer-info" ,  QVariant(QString, "EPSON Stylus TX105") ) (
0271     //                 "printer-make-and-model" ,  QVariant(QString, "EPSON TX105 Series") ) )  )
0272     // Get destinations with these attributes
0273     auto request = new KCupsRequest;
0274     connect(request, &KCupsRequest::finished, this, &PrinterModel::getDestsFinished);
0275     request->getPrinters(attrs);
0276 }
0277 
0278 void PrinterModel::insertDest(int pos, const KCupsPrinter &printer)
0279 {
0280     // Create the printer item
0281     auto stdItem = new QStandardItem(printer.name());
0282     stdItem->setData(printer.name(), DestName);
0283     stdItem->setIcon(printer.icon());
0284     // update the item
0285     updateDest(stdItem, printer);
0286 
0287     // insert the printer Item
0288     insertRow(pos, stdItem);
0289 }
0290 
0291 void PrinterModel::updateDest(QStandardItem *destItem, const KCupsPrinter &printer)
0292 {
0293     // store if the printer is the network default
0294     bool isDefault = printer.isDefault();
0295     if (destItem->data(DestIsDefault).isNull() || isDefault != destItem->data(DestIsDefault).toBool()) {
0296         destItem->setData(isDefault, DestIsDefault);
0297     }
0298 
0299     // store the printer state
0300     KCupsPrinter::Status state = printer.state();
0301     if (state != destItem->data(DestState)) {
0302         destItem->setData(state, DestState);
0303     }
0304     qCDebug(LIBKCUPS) << state << printer.name();
0305 
0306     // store if the printer is accepting jobs
0307     bool accepting = printer.isAcceptingJobs();
0308     if (accepting != destItem->data(DestIsAcceptingJobs)) {
0309         destItem->setData(accepting, DestIsAcceptingJobs);
0310     }
0311 
0312     // store the printer status message
0313     QString status = destStatus(state, printer.stateMsg(), accepting);
0314     if (status != destItem->data(DestStatus)) {
0315         destItem->setData(status, DestStatus);
0316     }
0317 
0318     bool paused = (state == KCupsPrinter::Stopped || !accepting);
0319     if (paused != destItem->data(DestIsPaused)) {
0320         destItem->setData(paused, DestIsPaused);
0321     }
0322 
0323     // store if the printer is shared
0324     bool shared = printer.isShared();
0325     if (shared != destItem->data(DestIsShared)) {
0326         destItem->setData(shared, DestIsShared);
0327     }
0328 
0329     // store if the printer is a class
0330     // the printer-type param is a flag
0331     bool isClass = printer.isClass();
0332     if (isClass != destItem->data(DestIsClass)) {
0333         destItem->setData(isClass, DestIsClass);
0334     }
0335 
0336     // store if the printer type
0337     // the printer-type param is a flag
0338     uint printerType = printer.type();
0339     if (printerType != destItem->data(DestType)) {
0340         destItem->setData(printerType, DestType);
0341         destItem->setData(printerType & CUPS_PRINTER_REMOTE, DestRemote);
0342     }
0343 
0344     // store the printer location
0345     QString location = printer.location();
0346     if (location.isEmpty() || location != destItem->data(DestLocation).toString()) {
0347         destItem->setData(location, DestLocation);
0348     }
0349 
0350     // store the printer icon name
0351     QString iconName = printer.iconName();
0352     if (iconName != destItem->data(DestIconName).toString()) {
0353         destItem->setData(iconName, DestIconName);
0354     }
0355 
0356     if (destItem->data(DestName).toString() != destItem->text()) {
0357         destItem->setText(destItem->data(DestName).toString());
0358     }
0359 
0360     // store the printer description
0361     QString description = printer.info();
0362     if (description.isEmpty() || description != destItem->data(DestDescription).toString()) {
0363         destItem->setData(description, DestDescription);
0364     }
0365 
0366     // store the printer kind
0367     QString kind = printer.makeAndModel();
0368     if (kind != destItem->data(DestKind)) {
0369         destItem->setData(kind, DestKind);
0370     }
0371 
0372     // store the printer commands
0373     QStringList commands = printer.commands();
0374     if (commands != destItem->data(DestCommands)) {
0375         destItem->setData(commands, DestCommands);
0376     }
0377 
0378     // store the printer URI
0379     QString uri = printer.deviceUri();
0380     if (uri != destItem->data(DestUri).toString()) {
0381         destItem->setData(uri, DestUri);
0382     }
0383 
0384     QString us = printer.uriSupported();
0385     if (us != destItem->data(DestUriSupported).toString()) {
0386         destItem->setData(us, DestUriSupported);
0387     }
0388 
0389     // printer member names for type=class
0390     const auto members = printer.memberNames();
0391     if (members != destItem->data(DestMemberNames)) {
0392         destItem->setData(members, DestMemberNames);
0393     }
0394 
0395     int markerChangeTime = printer.markerChangeTime();
0396     if (markerChangeTime != destItem->data(DestMarkerChangeTime)) {
0397         destItem->setData(printer.markerChangeTime(), DestMarkerChangeTime);
0398         const QVariantMap markers{{KCUPS_MARKER_CHANGE_TIME, printer.markerChangeTime()},
0399                                   {KCUPS_MARKER_COLORS, printer.argument(KCUPS_MARKER_COLORS)},
0400                                   {KCUPS_MARKER_LEVELS, printer.argument(KCUPS_MARKER_LEVELS)},
0401                                   {KCUPS_MARKER_NAMES, printer.argument(KCUPS_MARKER_NAMES)},
0402                                   {KCUPS_MARKER_TYPES, printer.argument(KCUPS_MARKER_TYPES)}};
0403         destItem->setData(markers, DestMarkers);
0404     }
0405 }
0406 
0407 int PrinterModel::destRow(const QString &destName)
0408 {
0409     // find the position of the jobId inside the model
0410     for (int i = 0; i < rowCount(); i++) {
0411         if (destName == item(i)->data(DestName).toString()) {
0412             return i;
0413         }
0414     }
0415     // -1 if not found
0416     return -1;
0417 }
0418 
0419 QString PrinterModel::destStatus(KCupsPrinter::Status state, const QString &message, bool isAcceptingJobs) const
0420 {
0421     switch (state) {
0422     case KCupsPrinter::Idle:
0423         if (message.isEmpty()) {
0424             return isAcceptingJobs ? i18n("Idle") : i18n("Idle, rejecting jobs");
0425         } else {
0426             return isAcceptingJobs ? i18n("Idle - '%1'", message) : i18n("Idle, rejecting jobs - '%1'", message);
0427         }
0428     case KCupsPrinter::Printing:
0429         if (message.isEmpty()) {
0430             return i18n("In use");
0431         } else {
0432             return i18n("In use - '%1'", message);
0433         }
0434     case KCupsPrinter::Stopped:
0435         if (message.isEmpty()) {
0436             return isAcceptingJobs ? i18n("Paused") : i18n("Paused, rejecting jobs");
0437         } else {
0438             return isAcceptingJobs ? i18n("Paused - '%1'", message) : i18n("Paused, rejecting jobs - '%1'", message);
0439         }
0440     default:
0441         if (message.isEmpty()) {
0442             return i18n("Unknown");
0443         } else {
0444             return i18n("Unknown - '%1'", message);
0445         }
0446     }
0447 }
0448 
0449 Qt::ItemFlags PrinterModel::flags(const QModelIndex &index) const
0450 {
0451     Q_UNUSED(index)
0452     return Qt::ItemIsSelectable | Qt::ItemIsEnabled;
0453 }
0454 
0455 void PrinterModel::insertUpdatePrinterName(const QString &printerName)
0456 {
0457     auto request = new KCupsRequest;
0458     connect(request, &KCupsRequest::finished, this, &PrinterModel::insertUpdatePrinterFinished);
0459     // TODO how do we know if it's a class if this DBus signal
0460     // does not tell us
0461     request->getPrinterAttributes(printerName, false, attrs);
0462 }
0463 
0464 void PrinterModel::insertUpdatePrinter(const QString &text,
0465                                        const QString &printerUri,
0466                                        const QString &printerName,
0467                                        uint printerState,
0468                                        const QString &printerStateReasons,
0469                                        bool printerIsAcceptingJobs)
0470 {
0471     Q_UNUSED(text)
0472     Q_UNUSED(printerUri)
0473     Q_UNUSED(printerState)
0474     Q_UNUSED(printerStateReasons)
0475     Q_UNUSED(printerIsAcceptingJobs)
0476 
0477     qCDebug(LIBKCUPS) << text << printerUri << printerName << printerState << printerStateReasons << printerIsAcceptingJobs;
0478     insertUpdatePrinterName(printerName);
0479 }
0480 
0481 void PrinterModel::insertUpdatePrinterFinished(KCupsRequest *request)
0482 {
0483     if (!request->hasError()) {
0484         const KCupsPrinters printers = request->printers();
0485         for (const KCupsPrinter &printer : printers) {
0486             // If there is a printer and it's not the current one add it
0487             // as a new destination
0488             int dest_row = destRow(printer.name());
0489             if (dest_row == -1) {
0490                 // not found, insert new one
0491                 insertDest(0, printer);
0492             } else {
0493                 // update the printer
0494                 updateDest(item(dest_row), printer);
0495             }
0496         }
0497     }
0498     setDisplayLocationHint();
0499     request->deleteLater();
0500 }
0501 
0502 void PrinterModel::printerRemovedName(const QString &printerName)
0503 {
0504     qCDebug(LIBKCUPS) << printerName;
0505 
0506     // Look for the removed printer
0507     int dest_row = destRow(printerName);
0508     if (dest_row != -1) {
0509         removeRows(dest_row, 1);
0510     }
0511     setDisplayLocationHint();
0512 }
0513 
0514 void PrinterModel::printerRemoved(const QString &text,
0515                                   const QString &printerUri,
0516                                   const QString &printerName,
0517                                   uint printerState,
0518                                   const QString &printerStateReasons,
0519                                   bool printerIsAcceptingJobs)
0520 {
0521     // REALLY? all these parameters just to say foo was deleted??
0522     Q_UNUSED(text)
0523     Q_UNUSED(printerUri)
0524     Q_UNUSED(printerState)
0525     Q_UNUSED(printerStateReasons)
0526     Q_UNUSED(printerIsAcceptingJobs)
0527     qCDebug(LIBKCUPS) << text << printerUri << printerName << printerState << printerStateReasons << printerIsAcceptingJobs;
0528 
0529     // Look for the removed printer
0530     int dest_row = destRow(printerName);
0531     if (dest_row != -1) {
0532         removeRows(dest_row, 1);
0533     }
0534     setDisplayLocationHint();
0535 }
0536 
0537 void PrinterModel::printerStateChanged(const QString &text,
0538                                        const QString &printerUri,
0539                                        const QString &printerName,
0540                                        uint printerState,
0541                                        const QString &printerStateReasons,
0542                                        bool printerIsAcceptingJobs)
0543 {
0544     qCDebug(LIBKCUPS) << text << printerUri << printerName << printerState << printerStateReasons << printerIsAcceptingJobs;
0545 }
0546 void PrinterModel::printerStopped(const QString &text,
0547                                   const QString &printerUri,
0548                                   const QString &printerName,
0549                                   uint printerState,
0550                                   const QString &printerStateReasons,
0551                                   bool printerIsAcceptingJobs)
0552 {
0553     qCDebug(LIBKCUPS) << text << printerUri << printerName << printerState << printerStateReasons << printerIsAcceptingJobs;
0554 }
0555 
0556 void PrinterModel::printerRestarted(const QString &text,
0557                                     const QString &printerUri,
0558                                     const QString &printerName,
0559                                     uint printerState,
0560                                     const QString &printerStateReasons,
0561                                     bool printerIsAcceptingJobs)
0562 {
0563     qCDebug(LIBKCUPS) << text << printerUri << printerName << printerState << printerStateReasons << printerIsAcceptingJobs;
0564 }
0565 
0566 void PrinterModel::printerShutdown(const QString &text,
0567                                    const QString &printerUri,
0568                                    const QString &printerName,
0569                                    uint printerState,
0570                                    const QString &printerStateReasons,
0571                                    bool printerIsAcceptingJobs)
0572 {
0573     qCDebug(LIBKCUPS) << text << printerUri << printerName << printerState << printerStateReasons << printerIsAcceptingJobs;
0574 }
0575 
0576 void PrinterModel::printerModified(const QString &text,
0577                                    const QString &printerUri,
0578                                    const QString &printerName,
0579                                    uint printerState,
0580                                    const QString &printerStateReasons,
0581                                    bool printerIsAcceptingJobs)
0582 {
0583     qCDebug(LIBKCUPS) << text << printerUri << printerName << printerState << printerStateReasons << printerIsAcceptingJobs;
0584     setDisplayLocationHint();
0585 }
0586 
0587 void PrinterModel::serverChanged(const QString &text)
0588 {
0589     qCDebug(LIBKCUPS) << text;
0590     update();
0591 }
0592 
0593 #include "moc_PrinterModel.cpp"