From b80cdd80fce98cf65a4c5eef9f7fa355e65bac27 Mon Sep 17 00:00:00 2001 From: Thomas Michel Date: Sat, 27 Jun 2015 17:48:36 +0200 Subject: [PATCH] added new import file selection page, bugfixes --- CarBudget.cpp | 2 + car.cpp | 104 +++++---- car.h | 1 + carmanager.cpp | 4 +- filemodel.cpp | 403 +++++++++++++++++++++++++++++++++ filemodel.h | 89 ++++++++ globals.cpp | 103 +++++++++ globals.h | 22 ++ harbour-carbudget.pro | 13 +- qml/pages/CarView.qml | 1 + qml/pages/DirectoryPage.qml | 106 +++++++++ qml/pages/SelectImportFile.qml | 31 ++- statfileinfo.cpp | 95 ++++++++ statfileinfo.h | 98 ++++++++ tank.cpp | 21 +- 15 files changed, 1025 insertions(+), 68 deletions(-) create mode 100644 filemodel.cpp create mode 100644 filemodel.h create mode 100644 globals.cpp create mode 100644 globals.h create mode 100644 qml/pages/DirectoryPage.qml create mode 100644 statfileinfo.cpp create mode 100644 statfileinfo.h diff --git a/CarBudget.cpp b/CarBudget.cpp index 4d58c32..8401e26 100644 --- a/CarBudget.cpp +++ b/CarBudget.cpp @@ -31,6 +31,7 @@ #include "station.h" #include "car.h" #include "carmanager.h" +#include "filemodel.h" #include #include @@ -65,6 +66,7 @@ int main(int argc, char *argv[]) qmlRegisterType( "harbour.carbudget",1,0,"Tire"); qmlRegisterType( "harbour.carbudget",1,0,"Tiremount"); qmlRegisterType( "harbour.carbudget",1,0,"Car"); + qmlRegisterType("harbour.carbudget", 1, 0, "FileModel"); CarManager manager; diff --git a/car.cpp b/car.cpp index 6bc6ffe..ccd57fe 100644 --- a/car.cpp +++ b/car.cpp @@ -74,6 +74,7 @@ void Car::db_init() void Car::db_load() { + db_loading=true; QSqlQuery query(this->db); _tanklist.clear(); @@ -88,6 +89,7 @@ void Car::db_load() { while(query.next()) { + qDebug() << "Adding tank " << query.value(2).toInt(); int id = query.value(0).toInt(); QDate date = query.value(1).toDate(); unsigned int distance = query.value(2).toInt(); @@ -100,14 +102,7 @@ void Car::db_load() Tank *tank = new Tank(date, distance, quantity, price, full, fueltype, station, id, note, this); _tanklist.append(tank); } - emit nbtankChanged(_tanklist.count()); - emit consumptionChanged(this->consumption()); - emit consumptionmaxChanged(this->consumptionmax()); - emit consumptionlastChanged(this->consumptionlast()); - emit consumptionminChanged(this->consumptionmin()); - emit fueltotalChanged(this->fueltotal()); - emit maxdistanceChanged(this->maxdistance()); - emit mindistanceChanged(this->mindistance()); + } else { @@ -230,12 +225,21 @@ void Car::db_load() { qDebug() << "Failed to load tiremounts: " << query.lastError(); } - qSort(_tanklist.begin(), _tanklist.end(), sortTankByDistance); - qSort(_costlist.begin(), _costlist.end(), sortCostByDate); - qSort(_stationlist.begin(), _stationlist.end(), sortStationByQuantity); - qSort(_fueltypelist.begin(), _fueltypelist.end(), sortFueltypeById); - qSort(_costtypelist.begin(), _costtypelist.end(), sortCosttypeById); - qSort(_tiremountlist.begin(),_tiremountlist.end(),sortTiremountByDistance); + if (!_tanklist.empty()) qSort(_tanklist.begin(), _tanklist.end(), sortTankByDistance); + if (!_costlist.empty()) qSort(_costlist.begin(), _costlist.end(), sortCostByDate); + if (!_stationlist.empty()) qSort(_stationlist.begin(), _stationlist.end(), sortStationByQuantity); + if (!_fueltypelist.empty()) qSort(_fueltypelist.begin(), _fueltypelist.end(), sortFueltypeById); + if (!_costtypelist.empty()) qSort(_costtypelist.begin(), _costtypelist.end(), sortCosttypeById); + if (!_tiremountlist.empty()) qSort(_tiremountlist.begin(),_tiremountlist.end(),sortTiremountByDistance); + db_loading=false; + nbtankChanged(_tanklist.count()); + emit consumptionChanged(this->consumption()); + emit consumptionmaxChanged(this->consumptionmax()); + emit consumptionlastChanged(this->consumptionlast()); + emit consumptionminChanged(this->consumptionmin()); + emit fueltotalChanged(this->fueltotal()); + emit maxdistanceChanged(this->maxdistance()); + emit mindistanceChanged(this->mindistance()); } int Car::db_get_version() @@ -320,6 +324,7 @@ Car::Car(CarManager *parent) : QObject(parent), _manager(parent) Car::Car(QString name, CarManager *parent) : QObject(parent), _manager(parent), _name(name), _nbtire(0),_buyingprice(0),_sellingprice(0),_lifetime(0) { this->db_init(); + db_loading=false; while(this->db_get_version() < DB_VERSION) { qDebug() << "Update configuation database " << this->db_get_version() << " >> " << DB_VERSION; @@ -359,10 +364,10 @@ unsigned int Car::nbtank() const double Car::consumption() const { + if (_tanklist.empty()) return 0.0; unsigned long int maxDistance = 0; unsigned long int minDistance = 999999999; double totalConsumption = 0; - foreach(Tank *tank, _tanklist) { if(tank->distance() > maxDistance) @@ -380,6 +385,7 @@ double Car::consumption() const break; } } + if (maxDistance==minDistance) return 0; return totalConsumption / ((maxDistance - minDistance)/ 100.0); } @@ -396,8 +402,10 @@ double Car::consumptionmax() const double Car::consumptionlast() const { + if (_tanklist.empty()) return 0.0; QList::const_iterator tank = _tanklist.constBegin(); - return (*tank)->consumption(); + if (*tank) return (*tank)->consumption(); + else return 0; } double Car::consumptionmin() const @@ -490,16 +498,13 @@ QQmlListProperty Car::tiremounts() const Tank *Car::previousTank(unsigned int distance) const { const Tank *previous = NULL; - + int currentPrevDistance=0; foreach(Tank *tank, _tanklist) { - if(previous == NULL && tank->distance() < distance) - { - previous = tank; - } - else if(tank->distance() < distance && tank->distance() > previous->distance()) + if ((tank->distance() < distance) && (tank->distance() > currentPrevDistance)) { previous = tank; + currentPrevDistance=tank->distance(); } } return previous; @@ -651,6 +656,7 @@ double Car::budget_cost_byType(unsigned int id) if (cost->costtype()==id) totalPrice += cost->cost(); } + if (maxdistance()==mindistance()) return 0; return totalPrice / ((maxdistance() - mindistance())/ 100.0); } double Car::budget_fuel_total() @@ -685,7 +691,7 @@ double Car::budget_fuel() break; } } - if(maxDistance == 0) return 0; + if((maxDistance == 0) || (maxDistance == minDistance)) return 0; return totalPrice / ((maxDistance - minDistance)/ 100.0); } double Car::budget_cost_total() @@ -701,7 +707,7 @@ double Car::budget_cost_total() double Car::budget_cost() { //returns costs for bills per 100KM - if (maxdistance()-mindistance() ==0) return 0; + if (maxdistance() ==mindistance()) return 0; return budget_cost_total() / ((maxdistance() - mindistance())/ 100.0); } double Car::budget_invest_total() @@ -712,6 +718,7 @@ double Car::budget_invest_total() double Car::budget_invest() { //returns bying costs per 100 KM + if (maxdistance()== mindistance()) return 0; QDate today = QDate::currentDate(); unsigned int monthsused = 1; double valuecosts; @@ -727,7 +734,7 @@ double Car::budget_invest() { monthsused++; } - if ((monthsused < _lifetime) && (_lifetime !=0)) + if ((monthsused < _lifetime) && (_lifetime !=0) ) valuecosts = (_buyingprice - _sellingprice)*monthsused/_lifetime; else valuecosts = (_buyingprice - _sellingprice); return valuecosts / ((maxdistance() - mindistance())/ 100.0); @@ -735,6 +742,7 @@ double Car::budget_invest() double Car::budget_tire() { //returns tire costs per 100km + if (maxdistance() == mindistance()) return 0; return budget_tire_total() / ((maxdistance() - mindistance())/ 100.0); } double Car::budget_tire_total() @@ -765,29 +773,35 @@ void Car::addNewTank(QDate date, unsigned int distance, double quantity, double _tanklist.append(tank); qSort(_tanklist.begin(), _tanklist.end(), sortTankByDistance); tank->save(); - emit nbtankChanged(_tanklist.count()); - emit consumptionChanged(this->consumption()); - emit consumptionmaxChanged(this->consumptionmax()); - emit consumptionlastChanged(this->consumptionlast()); - emit consumptionminChanged(this->consumptionmin()); - emit fueltotalChanged(this->fueltotal()); - emit maxdistanceChanged(this->maxdistance()); - emit tanksChanged(); + if (!db_loading) + { + emit nbtankChanged(_tanklist.count()); + emit consumptionChanged(this->consumption()); + emit consumptionmaxChanged(this->consumptionmax()); + emit consumptionlastChanged(this->consumptionlast()); + emit consumptionminChanged(this->consumptionmin()); + emit fueltotalChanged(this->fueltotal()); + emit maxdistanceChanged(this->maxdistance()); + emit tanksChanged(); + } } void Car::delTank(Tank *tank) { qDebug() << "Remove tank " << tank->id(); _tanklist.removeAll(tank); - qSort(_tanklist.begin(), _tanklist.end(), sortTankByDistance); + if (!_tanklist.empty()) qSort(_tanklist.begin(), _tanklist.end(), sortTankByDistance); tank->remove(); - emit nbtankChanged(_tanklist.count()); - emit consumptionChanged(this->consumption()); - emit consumptionmaxChanged(this->consumptionmax()); - emit consumptionlastChanged(this->consumptionlast()); - emit consumptionminChanged(this->consumptionmin()); - emit maxdistanceChanged(this->maxdistance()); - emit tanksChanged(); + if (!db_loading) + { + emit nbtankChanged(_tanklist.count()); + emit consumptionChanged(this->consumption()); + emit consumptionmaxChanged(this->consumptionmax()); + emit consumptionlastChanged(this->consumptionlast()); + emit consumptionminChanged(this->consumptionmin()); + emit maxdistanceChanged(this->maxdistance()); + emit tanksChanged(); + } tank->deleteLater(); } @@ -808,7 +822,7 @@ void Car::delFueltype(Fueltype *fueltype) { qDebug() << "Remove Fuel Type " << fueltype->id(); _fueltypelist.removeAll(fueltype); - qSort(_fueltypelist.begin(), _fueltypelist.end(), sortFueltypeById); + if (!_fueltypelist.empty()) qSort(_fueltypelist.begin(), _fueltypelist.end(), sortFueltypeById); QSqlQuery query(db); QString sql = QString("UPDATE TankList SET Fueltype = 0 WHERE fueltype=%1;").arg(fueltype->id()); @@ -879,7 +893,7 @@ void Car::delStation(Station *station) { qDebug() << "Remove Station " << station->id(); _stationlist.removeAll(station); - qSort(_stationlist.begin(), _stationlist.end(), sortStationByQuantity); + if (!_stationlist.empty()) qSort(_stationlist.begin(), _stationlist.end(), sortStationByQuantity); QSqlQuery query(db); QString sql = QString("UPDATE TankList SET station = 0 WHERE station=%1;").arg(station->id()); @@ -950,7 +964,7 @@ void Car::delCosttype(Costtype *costtype) { qDebug() << "Remove Cost Type " << costtype->id(); _costtypelist.removeAll(costtype); - qSort(_costtypelist.begin(), _costtypelist.end(), sortCosttypeById); + if (!_costtypelist.empty()) qSort(_costtypelist.begin(), _costtypelist.end(), sortCosttypeById); QSqlQuery query(db); QString sql = QString("UPDATE CostList SET costtype = 0 WHERE costtype=%1;").arg(costtype->id()); @@ -1019,7 +1033,7 @@ void Car::delCost(Cost *cost) { qDebug() << "Remove Cost " << cost->id(); _costlist.removeAll(cost); - qSort(_costlist.begin(), _costlist.end(), sortCostByDate); + if (!_costlist.empty()) qSort(_costlist.begin(), _costlist.end(), sortCostByDate); cost->remove(); emit costsChanged(); cost->deleteLater(); diff --git a/car.h b/car.h index ffff4d2..e3c021f 100644 --- a/car.h +++ b/car.h @@ -106,6 +106,7 @@ class Car : public QObject void db_upgrade_to_2(); void db_upgrade_to_3(); void db_upgrade_to_4(); + bool db_loading; public: QSqlDatabase db; diff --git a/carmanager.cpp b/carmanager.cpp index f6a3c38..d4f831a 100644 --- a/carmanager.cpp +++ b/carmanager.cpp @@ -376,9 +376,9 @@ void CarManager::importFromMyCar(QString filename, QString name) void CarManager::importFromFuelpad(QString filename, QString name) { + qDebug() << "Importing from Fuelpad"; createCar(name); selectCar(name); - filename=getenv("HOME")+QString("/")+filename; QSqlDatabase db; db = QSqlDatabase::addDatabase("QSQLITE","fuelpaddb"); db.setDatabaseName(filename); @@ -420,6 +420,7 @@ void CarManager::importFromFuelpad(QString filename, QString name) } if (query.next()) t_id = query.value(0).toInt(); + qDebug() << "Car id: " << t_id; if(query.exec(QString("SELECT day,km,fill,price,service,oil,tires,insurance,other,notes FROM record WHERE carid=%1;").arg(t_id))) { while(query.next()) @@ -464,7 +465,6 @@ QString CarManager::getEnv(QString name) QStringList CarManager::checkFuelpadDBforCars( QString name) { - name=getenv("HOME")+QString("/")+name; QStringList fuelpadcars; QSqlDatabase db; db = QSqlDatabase::addDatabase("QSQLITE","fuelpaddb"); diff --git a/filemodel.cpp b/filemodel.cpp new file mode 100644 index 0000000..3fb28e5 --- /dev/null +++ b/filemodel.cpp @@ -0,0 +1,403 @@ +#include "filemodel.h" +#include +#include +#include "globals.h" + +enum { + FilenameRole = Qt::UserRole + 1, + FileKindRole = Qt::UserRole + 2, + FileIconRole = Qt::UserRole + 3, + PermissionsRole = Qt::UserRole + 4, + SizeRole = Qt::UserRole + 5, + LastModifiedRole = Qt::UserRole + 6, + CreatedRole = Qt::UserRole + 7, + IsDirRole = Qt::UserRole + 8, + IsLinkRole = Qt::UserRole + 9, + SymLinkTargetRole = Qt::UserRole + 10, + IsSelectedRole = Qt::UserRole + 11 +}; + +FileModel::FileModel(QObject *parent) : + QAbstractListModel(parent), + m_selectedFileCount(0), + m_active(false), + m_dirty(false) +{ + m_dir = ""; + m_watcher = new QFileSystemWatcher(this); + connect(m_watcher, SIGNAL(directoryChanged(const QString&)), this, SLOT(refresh())); + connect(m_watcher, SIGNAL(fileChanged(const QString&)), this, SLOT(refresh())); + +} + +FileModel::~FileModel() +{ +} + +int FileModel::rowCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent); + return m_files.count(); +} + +QVariant FileModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid() || index.row() > m_files.size()-1) + return QVariant(); + + StatFileInfo info = m_files.at(index.row()); + switch (role) { + + case Qt::DisplayRole: + case FilenameRole: + return info.fileName(); + + case FileKindRole: + return info.kind(); + + case FileIconRole: + return infoToIconName(info); + + case PermissionsRole: + return permissionsToString(info.permissions()); + + case SizeRole: + if (info.isSymLink() && info.isDirAtEnd()) return tr("dir-link"); + if (info.isDir()) return tr("dir"); + return filesizeToString(info.size()); + + case LastModifiedRole: + return datetimeToString(info.lastModified()); + + case CreatedRole: + return datetimeToString(info.created()); + + case IsDirRole: + return info.isDirAtEnd(); + + case IsLinkRole: + return info.isSymLink(); + + case SymLinkTargetRole: + return info.symLinkTarget(); + + case IsSelectedRole: + return info.isSelected(); + + default: + return QVariant(); + } +} + +QHash FileModel::roleNames() const +{ + QHash roles = QAbstractListModel::roleNames(); + roles.insert(FilenameRole, QByteArray("filename")); + roles.insert(FileKindRole, QByteArray("filekind")); + roles.insert(FileIconRole, QByteArray("fileIcon")); + roles.insert(PermissionsRole, QByteArray("permissions")); + roles.insert(SizeRole, QByteArray("size")); + roles.insert(LastModifiedRole, QByteArray("modified")); + roles.insert(CreatedRole, QByteArray("created")); + roles.insert(IsDirRole, QByteArray("isDir")); + roles.insert(IsLinkRole, QByteArray("isLink")); + roles.insert(SymLinkTargetRole, QByteArray("symLinkTarget")); + roles.insert(IsSelectedRole, QByteArray("isSelected")); + return roles; +} + +int FileModel::fileCount() const +{ + return m_files.count(); +} + +QString FileModel::errorMessage() const +{ + return m_errorMessage; +} + +void FileModel::setDir(QString dir) +{ + if (m_dir == dir) + return; + + // update watcher to watch the new directory + if (!m_dir.isEmpty()) + m_watcher->removePath(m_dir); + if (!dir.isEmpty()) + m_watcher->addPath(dir); + + m_dir = dir; + + readDirectory(); + m_dirty = false; + + emit dirChanged(); +} + +QString FileModel::appendPath(QString dirName) +{ + return QDir::cleanPath(QDir(m_dir).absoluteFilePath(dirName)); +} + +void FileModel::setActive(bool active) +{ + if (m_active == active) + return; + + m_active = active; + emit activeChanged(); + + if (m_dirty) + readDirectory(); + + m_dirty = false; +} + +QString FileModel::parentPath() +{ + return QDir::cleanPath(QDir(m_dir).absoluteFilePath("..")); +} + +QString FileModel::fileNameAt(int fileIndex) +{ + if (fileIndex < 0 || fileIndex >= m_files.count()) + return QString(); + + return m_files.at(fileIndex).absoluteFilePath(); +} + +void FileModel::toggleSelectedFile(int fileIndex) +{ + if (!m_files.at(fileIndex).isSelected()) { + StatFileInfo info = m_files.at(fileIndex); + info.setSelected(true); + m_files[fileIndex] = info; + m_selectedFileCount++; + } else { + StatFileInfo info = m_files.at(fileIndex); + info.setSelected(false); + m_files[fileIndex] = info; + m_selectedFileCount--; + } + // emit signal for views + QModelIndex topLeft = index(fileIndex, 0); + QModelIndex bottomRight = index(fileIndex, 0); + emit dataChanged(topLeft, bottomRight); + + emit selectedFileCountChanged(); +} + +void FileModel::clearSelectedFiles() +{ + QMutableListIterator iter(m_files); + int row = 0; + while (iter.hasNext()) { + StatFileInfo &info = iter.next(); + info.setSelected(false); + // emit signal for views + QModelIndex topLeft = index(row, 0); + QModelIndex bottomRight = index(row, 0); + emit dataChanged(topLeft, bottomRight); + row++; + } + m_selectedFileCount = 0; + emit selectedFileCountChanged(); +} + +void FileModel::selectAllFiles() +{ + QMutableListIterator iter(m_files); + int row = 0; + while (iter.hasNext()) { + StatFileInfo &info = iter.next(); + info.setSelected(true); + // emit signal for views + QModelIndex topLeft = index(row, 0); + QModelIndex bottomRight = index(row, 0); + emit dataChanged(topLeft, bottomRight); + row++; + } + m_selectedFileCount = m_files.count(); + emit selectedFileCountChanged(); +} + +QStringList FileModel::selectedFiles() const +{ + if (m_selectedFileCount == 0) + return QStringList(); + + QStringList filenames; + foreach (const StatFileInfo &info, m_files) { + if (info.isSelected()) + filenames.append(info.absoluteFilePath()); + } + return filenames; +} + +void FileModel::refresh() +{ + if (!m_active) { + m_dirty = true; + return; + } + + refreshEntries(); + m_dirty = false; +} + +void FileModel::refreshFull() +{ + if (!m_active) { + m_dirty = true; + return; + } + + readDirectory(); + m_dirty = false; +} + +void FileModel::readDirectory() +{ + // wrapped in reset model methods to get views notified + beginResetModel(); + + m_files.clear(); + m_errorMessage = ""; + + if (!m_dir.isEmpty()) + readAllEntries(); + + endResetModel(); + emit fileCountChanged(); + emit errorMessageChanged(); + recountSelectedFiles(); +} + +void FileModel::recountSelectedFiles() +{ + int count = 0; + foreach (const StatFileInfo &info, m_files) { + if (info.isSelected()) + count++; + } + if (m_selectedFileCount != count) { + m_selectedFileCount = count; + emit selectedFileCountChanged(); + } +} + +void FileModel::readAllEntries() +{ + QDir dir(m_dir); + if (!dir.exists()) { + m_errorMessage = tr("Folder does not exist"); + return; + } + if (access(m_dir, R_OK) == -1) { + m_errorMessage = tr("No permission to read the folder"); + return; + } + + + dir.setFilter(QDir::AllDirs | QDir::Files | QDir::NoDotAndDotDot | QDir::System ); + dir.setSorting(QDir::Name | QDir::DirsFirst); + + QStringList fileList = dir.entryList(); + foreach (QString filename, fileList) { + QString fullpath = dir.absoluteFilePath(filename); + StatFileInfo info(fullpath); + m_files.append(info); + } +} + +void FileModel::refreshEntries() +{ + m_errorMessage = ""; + + // empty dir name + if (m_dir.isEmpty()) { + clearModel(); + emit errorMessageChanged(); + return; + } + + QDir dir(m_dir); + if (!dir.exists()) { + clearModel(); + m_errorMessage = tr("Folder does not exist"); + emit errorMessageChanged(); + return; + } + if (access(m_dir, R_OK) == -1) { + clearModel(); + m_errorMessage = tr("No permission to read the folder"); + emit errorMessageChanged(); + return; + } + + dir.setFilter(QDir::AllDirs | QDir::Files | QDir::NoDotAndDotDot | QDir::System ); + + dir.setSorting(QDir::Name | QDir::DirsFirst); + + // read all files + QList newFiles; + + QStringList fileList = dir.entryList(); + foreach (QString filename, fileList) { + QString fullpath = dir.absoluteFilePath(filename); + StatFileInfo info(fullpath); + newFiles.append(info); + } + + int oldFileCount = m_files.count(); + + // compare old and new files and do removes if needed + for (int i = m_files.count()-1; i >= 0; --i) { + StatFileInfo data = m_files.at(i); + if (!filesContains(newFiles, data)) { + beginRemoveRows(QModelIndex(), i, i); + m_files.removeAt(i); + endRemoveRows(); + } + } + // compare old and new files and do inserts if needed + for (int i = 0; i < newFiles.count(); ++i) { + StatFileInfo data = newFiles.at(i); + if (!filesContains(m_files, data)) { + beginInsertRows(QModelIndex(), i, i); + m_files.insert(i, data); + endInsertRows(); + } + } + + if (m_files.count() != oldFileCount) + emit fileCountChanged(); + + emit errorMessageChanged(); + recountSelectedFiles(); +} + +void FileModel::clearModel() +{ + beginResetModel(); + m_files.clear(); + endResetModel(); + emit fileCountChanged(); +} + +bool FileModel::filesContains(const QList &files, const StatFileInfo &fileData) const +{ + // check if list contains fileData with relevant info + foreach (const StatFileInfo &f, files) { + if (f.fileName() == fileData.fileName() && + f.size() == fileData.size() && + f.permissions() == fileData.permissions() && + f.lastModified() == fileData.lastModified() && + f.isSymLink() == fileData.isSymLink() && + f.isDirAtEnd() == fileData.isDirAtEnd()) { + return true; + } + } + return false; +} diff --git a/filemodel.h b/filemodel.h new file mode 100644 index 0000000..80049fd --- /dev/null +++ b/filemodel.h @@ -0,0 +1,89 @@ +#ifndef FILEMODEL_H +#define FILEMODEL_H + +#include +#include +#include +#include "statfileinfo.h" + +/** + * @brief The FileModel class can be used as a model in a ListView to display a list of files + * in the current directory. It has methods to change the current directory and to access + * file info. + * It also actively monitors the directory. If the directory changes, then the model is + * updated automatically if active is true. If active is false, then the directory is + * updated when active becomes true. + * + * Original code by karip in filebrowser for Sailfish OS + */ + +class FileModel : public QAbstractListModel +{ + Q_OBJECT + Q_PROPERTY(QString dir READ dir() WRITE setDir(QString) NOTIFY dirChanged()) + Q_PROPERTY(int fileCount READ fileCount() NOTIFY fileCountChanged()) + Q_PROPERTY(QString errorMessage READ errorMessage() NOTIFY errorMessageChanged()) + Q_PROPERTY(bool active READ active() WRITE setActive(bool) NOTIFY activeChanged()) + Q_PROPERTY(int selectedFileCount READ selectedFileCount() NOTIFY selectedFileCountChanged()) + +public: + explicit FileModel(QObject *parent = 0); + ~FileModel(); + + // methods needed by ListView + int rowCount(const QModelIndex &parent = QModelIndex()) const; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; + QHash roleNames() const; + + // property accessors + QString dir() const { return m_dir; } + void setDir(QString dir); + int fileCount() const; + QString errorMessage() const; + bool active() const { return m_active; } + void setActive(bool active); + int selectedFileCount() const { return m_selectedFileCount; } + + // methods accessible from QML + Q_INVOKABLE QString appendPath(QString dirName); + Q_INVOKABLE QString parentPath(); + Q_INVOKABLE QString fileNameAt(int fileIndex); + + // file selection + Q_INVOKABLE void toggleSelectedFile(int fileIndex); + Q_INVOKABLE void clearSelectedFiles(); + Q_INVOKABLE void selectAllFiles(); + Q_INVOKABLE QStringList selectedFiles() const; + +public slots: + // reads the directory and inserts/removes model items as needed + Q_INVOKABLE void refresh(); + // reads the directory and sets all model items + Q_INVOKABLE void refreshFull(); + +signals: + void dirChanged(); + void fileCountChanged(); + void errorMessageChanged(); + void activeChanged(); + void selectedFileCountChanged(); + +private slots: + void readDirectory(); + +private: + void recountSelectedFiles(); + void readAllEntries(); + void refreshEntries(); + void clearModel(); + bool filesContains(const QList &files, const StatFileInfo &fileData) const; + + QString m_dir; + QList m_files; + int m_selectedFileCount; + QString m_errorMessage; + bool m_active; + bool m_dirty; + QFileSystemWatcher *m_watcher; +}; +#endif // FIELMODEL_H diff --git a/globals.cpp b/globals.cpp new file mode 100644 index 0000000..eb1f89a --- /dev/null +++ b/globals.cpp @@ -0,0 +1,103 @@ + +#include "globals.h" +#include +#include + +QString suffixToIconName(QString suffix) +{ + // only formats that are understood by File Browser or Sailfish get a special icon + if (suffix == "txt") + return "file-txt"; + if (suffix == "rpm") + return "file-rpm"; + if (suffix == "apk") + return "file-apk"; + if (suffix == "png" || suffix == "jpeg" || suffix == "jpg" || + suffix == "gif") + return "file-image"; + if (suffix == "wav" || suffix == "mp3" || suffix == "flac" || + suffix == "aac" || suffix == "ogg" || suffix == "m4a") + return "file-audio"; + if (suffix == "mp4" || suffix == "m4v") + return "file-video"; + + return "file"; +} + +QString permissionsToString(QFile::Permissions permissions) +{ + char str[] = "---------"; + if (permissions & 0x4000) str[0] = 'r'; + if (permissions & 0x2000) str[1] = 'w'; + if (permissions & 0x1000) str[2] = 'x'; + if (permissions & 0x0040) str[3] = 'r'; + if (permissions & 0x0020) str[4] = 'w'; + if (permissions & 0x0010) str[5] = 'x'; + if (permissions & 0x0004) str[6] = 'r'; + if (permissions & 0x0002) str[7] = 'w'; + if (permissions & 0x0001) str[8] = 'x'; + return QString::fromLatin1(str); +} + +QString filesizeToString(qint64 filesize) +{ + // convert to kB, MB, GB: use 1000 instead of 1024 as divisor because it seems to be + // the usual way to display file size (like on Ubuntu) + QLocale locale; + if (filesize < 1000LL) + return QObject::tr("%1 bytes").arg(locale.toString(filesize)); + + if (filesize < 1000000LL) + return QObject::tr("%1 kB").arg(locale.toString((double)filesize/1000.0, 'f', 2)); + + if (filesize < 1000000000LL) + return QObject::tr("%1 MB").arg(locale.toString((double)filesize/1000000.0, 'f', 2)); + + return QObject::tr("%1 GB").arg(locale.toString((double)filesize/1000000000.0, 'f', 2)); +} + +QString datetimeToString(QDateTime datetime) +{ + QLocale locale; + + // return time for today or date for older + if (datetime.date() == QDate::currentDate()) + return locale.toString(datetime.time(), QLocale::NarrowFormat); + + return locale.toString(datetime.date(), QLocale::NarrowFormat); +} + +QString infoToIconName(const StatFileInfo &info) +{ + if (info.isSymLink() && info.isDirAtEnd()) return "folder-link"; + if (info.isDir()) return "folder"; + if (info.isSymLink()) return "link"; + if (info.isFileAtEnd()) { + QString suffix = info.suffix().toLower(); + return suffixToIconName(suffix); + } + return "file"; +} + +int access(QString filename, int how) +{ + QByteArray fab = filename.toUtf8(); + char *fn = fab.data(); + return access(fn, how); +} + +QString execute(QString command, QStringList arguments, bool mergeErrorStream) +{ + QProcess process; + process.setReadChannel(QProcess::StandardOutput); + if (mergeErrorStream) + process.setProcessChannelMode(QProcess::MergedChannels); + process.start(command, arguments); + if (!process.waitForStarted()) + return QString(); + if (!process.waitForFinished()) + return QString(); + + QByteArray result = process.readAll(); + return QString::fromUtf8(result); +} diff --git a/globals.h b/globals.h new file mode 100644 index 0000000..17d25f5 --- /dev/null +++ b/globals.h @@ -0,0 +1,22 @@ +#ifndef GLOBALS_H +#define GLOBALS_H + +#include +#include +#include +#include "statfileinfo.h" + +// Global functions + +QString suffixToIconName(QString suffix); +QString permissionsToString(QFile::Permissions permissions); +QString filesizeToString(qint64 filesize); +QString datetimeToString(QDateTime datetime); + +QString infoToIconName(const StatFileInfo &info); + +int access(QString filename, int how); + +QString execute(QString command, QStringList arguments, bool mergeErrorStream); + +#endif // GLOBALS_H diff --git a/harbour-carbudget.pro b/harbour-carbudget.pro index ff38651..e9b5088 100644 --- a/harbour-carbudget.pro +++ b/harbour-carbudget.pro @@ -21,7 +21,10 @@ SOURCES += CarBudget.cpp \ fueltype.cpp \ costtype.cpp \ carevent.cpp \ - tiremount.cpp + tiremount.cpp \ + filemodel.cpp \ + statfileinfo.cpp \ + globals.cpp lupdate_only{ SOURCES += qml/*.qml \ @@ -60,7 +63,8 @@ OTHER_FILES += qml/cover/CoverPage.qml \ qml/pages/CostStatistics.qml \ qml/pages/TiremountView.qml \ qml/pages/TiremountEdit.qml \ - qml/pages/ConsumptionStatistics.qml + qml/pages/ConsumptionStatistics.qml \ + qml/pages/DirectoryPage.qml HEADERS += \ tank.h \ @@ -72,7 +76,10 @@ HEADERS += \ fueltype.h \ costtype.h \ carevent.h \ - tiremount.h + tiremount.h \ + filemodel.h \ + statfileinfo.h \ + globals.h TRANSLATIONS = i18n/de_DE.ts \ i18n/fr_FR.ts \ diff --git a/qml/pages/CarView.qml b/qml/pages/CarView.qml index 07a735d..c89e7e7 100644 --- a/qml/pages/CarView.qml +++ b/qml/pages/CarView.qml @@ -31,6 +31,7 @@ Page { MenuItem { text: qsTr("Import Car") onClicked: pageStack.push(Qt.resolvedUrl("SelectImportFile.qml")) + //onClicked: pageStack.push(Qt.resolvedUrl("DirectoryPage.qml")) } MenuItem { text: qsTr("Create new car") diff --git a/qml/pages/DirectoryPage.qml b/qml/pages/DirectoryPage.qml new file mode 100644 index 0000000..9e81009 --- /dev/null +++ b/qml/pages/DirectoryPage.qml @@ -0,0 +1,106 @@ +import QtQuick 2.0 +import Sailfish.Silica 1.0 +import harbour.carbudget 1.0 + +//import "functions.js" as Functions +//import "../components" + +Page { + id: page + allowedOrientations: Orientation.All + property string dir: "/" + property bool initial: false // this is set to true if the page is initial page + property bool remorsePopupActive: false // set to true when remorsePopup is active + property bool remorseItemActive: false // set to true when remorseItem is active (item level) + + FileModel { + id: fileModel + dir: page.dir + // page.status does not exactly work - root folder seems to be active always?? + active: page.status === PageStatus.Active + } + + + + SilicaListView { + id: fileList + anchors.fill: parent + clip: true + + model: fileModel + + VerticalScrollDecorator { flickable: fileList } + + PullDownMenu { + MenuItem { + text: qsTr("Settings") + onClicked: pageStack.push(Qt.resolvedUrl("SettingsPage.qml")) + } + } + + header: PageHeader { + title: qsTr("Import File") + } + + delegate: ListItem { + id: fileItem + menu: contextMenu + width: ListView.view.width + contentHeight: listLabel.height + Row { + Label { + id: listLabel + anchors.fill: parent + text: filename + color: Theme.primaryColor + } +/* + onClicked: { + if (model.isDir) + pageStack.push(Qt.resolvedUrl("DirectoryPage.qml"), + { dir: fileModel.appendPath(listLabel.text) }); + else + pageStack.push(Qt.resolvedUrl("FilePage.qml"), + { file: fileModel.appendPath(listLabel.text) }); + } + */ + MouseArea { + width: 90 + height: parent.height + onClicked: { + fileModel.toggleSelectedFile(index); + selectionPanel.open = (fileModel.selectedFileCount > 0); + selectionPanel.overrideText = ""; + } + } + + + } + + + // context menu is activated with long press + Component { + id: contextMenu + ContextMenu { + MenuItem { + text: qsTr("Cut") + onClicked: engine.cutFiles([ fileModel.fileNameAt(index) ]); + } + } + } + } + + // text if no files or error message + Text { + width: parent.width + anchors.leftMargin: Theme.paddingLarge + anchors.rightMargin: Theme.paddingLarge + horizontalAlignment: Qt.AlignHCenter + y: -fileList.contentY + 100 + visible: fileModel.fileCount === 0 || fileModel.errorMessage !== "" + text: fileModel.errorMessage !== "" ? fileModel.errorMessage : qsTr("No files") + color: Theme.highlightColor + } + } + +} diff --git a/qml/pages/SelectImportFile.qml b/qml/pages/SelectImportFile.qml index 2c0c63c..557d94d 100644 --- a/qml/pages/SelectImportFile.qml +++ b/qml/pages/SelectImportFile.qml @@ -24,7 +24,9 @@ import harbour.carbudget 1.0 import Qt.labs.folderlistmodel 2.1 Page { + id:page allowedOrientations: Orientation.All + property string dir: manager.getEnv("HOME") SilicaListView { VerticalScrollDecorator {} @@ -36,24 +38,33 @@ Page { anchors.fill: parent leftMargin: Theme.paddingMedium rightMargin: Theme.paddingMedium - model:folderModel + model:fileModel - FolderListModel { - id: folderModel - folder: "file:///$HOME" - nameFilters: ["*.xml","*.db"] - showDirs: false + FileModel { + id: fileModel + dir: page.dir + // page.status does not exactly work - root folder seems to be active always?? + //active: page.status === PageStatus.Active } delegate: ListItem { width: parent.width - Theme.paddingMedium - Theme.paddingMedium showMenuOnPressAndHold: true - onClicked: pageStack.push(Qt.resolvedUrl(checkFileType(filename.text)), { filename: filename.text }) + //onClicked: pageStack.push(Qt.resolvedUrl(checkFileType(filename.text)), { filename: filename.text }) + onClicked: { + if (model.isDir) + pageStack.push(Qt.resolvedUrl("SelectImportFile.qml"), + { dir: fileModel.appendPath(txtfilename.text) }); + else + pageStack.push(Qt.resolvedUrl(checkFileType(txtfilename.text)), + { filename: dir+"/"+txtfilename.text }); + + } menu: ContextMenu { MenuItem { text: qsTr("Import") - onClicked: pageStack.push(Qt.resolvedUrl(checkFileType(filename.text)), { filename: filename.text }) + onClicked: pageStack.push(Qt.resolvedUrl(checkFileType(txtfilename.text)), { filename: dir+"/"+txtfilename.text }) } } @@ -65,8 +76,8 @@ Page { width: parent.width Text { - id: filename - text: fileName + id: txtfilename + text: filename font.family: Theme.fontFamily font.pixelSize: Theme.fontSizeSmall color: Theme.primaryColor diff --git a/statfileinfo.cpp b/statfileinfo.cpp new file mode 100644 index 0000000..927a489 --- /dev/null +++ b/statfileinfo.cpp @@ -0,0 +1,95 @@ +#include "statfileinfo.h" + +StatFileInfo::StatFileInfo() : + m_filename(""), m_selected(false) +{ + refresh(); +} + +StatFileInfo::StatFileInfo(QString filename) : + m_filename(filename), m_selected(false) +{ + refresh(); +} + +StatFileInfo::~StatFileInfo() +{ +} + +void StatFileInfo::setFile(QString filename) +{ + m_filename = filename; + refresh(); +} + +QString StatFileInfo::kind() const +{ + if (isSymLink()) return "l"; + if (isDir()) return "d"; + if (isBlk()) return "b"; + if (isChr()) return "c"; + if (isFifo()) return "p"; + if (isSocket()) return "s"; + if (isFile()) return "-"; + return "?"; +} + +bool StatFileInfo::exists() const +{ + return m_fileInfo.exists(); +} + +bool StatFileInfo::isSafeToRead() const +{ + // it is safe to read non-existing files + if (!exists()) + return true; + + // check the file is a regular file and not a special file + return isFileAtEnd(); +} + +bool StatFileInfo::isSymLinkBroken() const +{ + // if it is a symlink but it doesn't exist, then it is broken + if (m_fileInfo.isSymLink() && !m_fileInfo.exists()) + return true; + return false; +} + +void StatFileInfo::setSelected(bool selected) +{ + m_selected = selected; +} + +void StatFileInfo::refresh() +{ + memset(&m_stat, 0, sizeof(m_stat)); + memset(&m_lstat, 0, sizeof(m_lstat)); + + m_fileInfo = QFileInfo(m_filename); + if (m_filename.isEmpty()) + return; + + QByteArray ba = m_filename.toUtf8(); + char *fn = ba.data(); + + // check the file without following symlinks + int res = lstat(fn, &m_lstat); + if (res != 0) { // if error, then set to undefined + m_lstat.st_mode = 0; + } + // if not symlink, then just copy lstat data to stat + if (!S_ISLNK(m_lstat.st_mode)) { + memcpy(&m_stat, &m_lstat, sizeof(m_stat)); + return; + } + + // check the file after following possible symlinks + res = stat(fn, &m_stat); + if (res != 0) { // if error, then set to undefined + m_stat.st_mode = 0; + } + +} + diff --git a/statfileinfo.h b/statfileinfo.h new file mode 100644 index 0000000..a40d2f5 --- /dev/null +++ b/statfileinfo.h @@ -0,0 +1,98 @@ +#ifndef STATFILEINFO_H +#define STATFILEINFO_H + +#include +#include +#include +#include + +/** + * @brief The StatFileInfo class is like QFileInfo, but has more detailed information about file types. +* Original code by karip in filebrowser for Sailfish OS + */ + +class StatFileInfo +{ +public: + explicit StatFileInfo(); + explicit StatFileInfo(QString filename); + ~StatFileInfo(); + + void setFile(QString filename); + QString fileName() const { return m_fileInfo.fileName(); } + + // these inspect the file itself without following symlinks + + // directory + bool isDir() const { return S_ISDIR(m_lstat.st_mode); } + // symbolic link + bool isSymLink() const { return S_ISLNK(m_lstat.st_mode); } + // block special file + bool isBlk() const { return S_ISBLK(m_lstat.st_mode); } + // character special file + bool isChr() const { return S_ISCHR(m_lstat.st_mode); } + // pipe of FIFO special file + bool isFifo() const { return S_ISFIFO(m_lstat.st_mode); } + // socket + bool isSocket() const { return S_ISSOCK(m_lstat.st_mode); } + // regular file + bool isFile() const { return S_ISREG(m_lstat.st_mode); } + // system file (not a dir, regular file or symlink) + bool isSystem() const { return !S_ISDIR(m_lstat.st_mode) && !S_ISREG(m_lstat.st_mode) && + !S_ISLNK(m_lstat.st_mode); } + + // these inspect the file or if it is a symlink, then its target end point + + // directory + bool isDirAtEnd() const { return S_ISDIR(m_stat.st_mode); } + // block special file + bool isBlkAtEnd() const { return S_ISBLK(m_stat.st_mode); } + // character special file + bool isChrAtEnd() const { return S_ISCHR(m_stat.st_mode); } + // pipe of FIFO special file + bool isFifoAtEnd() const { return S_ISFIFO(m_stat.st_mode); } + // socket + bool isSocketAtEnd() const { return S_ISSOCK(m_stat.st_mode); } + // regular file + bool isFileAtEnd() const { return S_ISREG(m_stat.st_mode); } + // system file (not a dir or regular file) + bool isSystemAtEnd() const { return !S_ISDIR(m_stat.st_mode) && !S_ISREG(m_stat.st_mode); } + + // these inspect the file or if it is a symlink, then its target end point + + QString kind() const; + QFile::Permissions permissions() const { return m_fileInfo.permissions(); } + QString group() const { return m_fileInfo.group(); } + uint groupId() const { return m_fileInfo.groupId(); } + QString owner() const { return m_fileInfo.owner(); } + uint ownerId() const { return m_fileInfo.ownerId(); } + qint64 size() const { return m_fileInfo.size(); } + QDateTime lastModified() const { return m_fileInfo.lastModified(); } + QDateTime created() const { return m_fileInfo.created(); } + bool exists() const; + bool isSafeToRead() const; + + // path accessors + + QDir absoluteDir() const { return m_fileInfo.absoluteDir(); } + QString absolutePath() const { return m_fileInfo.absolutePath(); } + QString absoluteFilePath() const { return m_fileInfo.absoluteFilePath(); } + QString suffix() const { return m_fileInfo.suffix(); } + QString symLinkTarget() const { return m_fileInfo.symLinkTarget(); } + bool isSymLinkBroken() const; + + // selection + void setSelected(bool selected); + bool isSelected() const { return m_selected; } + + void refresh(); + +private: + QString m_filename; + QFileInfo m_fileInfo; + struct stat m_stat; // after following possible symlinks + struct stat m_lstat; // file itself without following symlinks + bool m_selected; +}; + +#endif // STATFILEINFO_H diff --git a/tank.cpp b/tank.cpp index 592e9e6..bfe027e 100644 --- a/tank.cpp +++ b/tank.cpp @@ -68,6 +68,7 @@ double Tank::price() const double Tank::priceu() const { + if (_quantity == 0) return 0; return _price / _quantity; } @@ -96,17 +97,21 @@ QString Tank::stationname() const double Tank::consumption() const { + if (!full()) return 0.0; const Tank *previous = _car->previousTank(_distance); double quant = this->quantity(); - - if((previous == NULL) || (!full())) - return 0; - while((previous != NULL) && (!(previous->full()))) { - quant += previous->quantity(); - previous = _car->previousTank(previous->distance()); + while(previous != NULL) + { + if (!(previous->full())) + { + qDebug() << "prevous distance is " << previous->quantity(); + quant += previous->quantity(); + previous = _car->previousTank(previous->distance()); + } + else break; } - if(previous == NULL) - return 0; + if (previous==NULL) return 0.0; + if (_distance ==previous->distance()) return 0.0; return quant / ((_distance - previous->distance()) / 100.0); }