├── ledgeritem.h ├── dateedit.h ├── main.cpp ├── ledgeritem.cpp ├── myfileinfo.h ├── dateedit.cpp ├── Accounting.pro ├── .gitignore ├── ledgervheader.h ├── ledgerdelegate.h ├── ledgerview.h ├── ledger.h ├── mainwindow.h ├── commands.h ├── myfileinfo.cpp ├── commands.cpp ├── ledgervheader.cpp ├── ledgerdelegate.cpp ├── mainwindow.ui ├── ledgerview.cpp ├── ledger.cpp └── mainwindow.cpp /ledgeritem.h: -------------------------------------------------------------------------------- 1 | #ifndef LEDGERITEM_H 2 | #define LEDGERITEM_H 3 | 4 | #include 5 | 6 | class LedgerItem : public QStandardItem 7 | { 8 | public: 9 | LedgerItem() : QStandardItem() {} 10 | virtual bool operator<(const QStandardItem &other) const Q_DECL_OVERRIDE; 11 | }; 12 | 13 | #endif // LEDGERITEM_H 14 | -------------------------------------------------------------------------------- /dateedit.h: -------------------------------------------------------------------------------- 1 | #ifndef DATEEDIT_H 2 | #define DATEEDIT_H 3 | 4 | #include 5 | 6 | class DateEdit : public QDateEdit 7 | { 8 | Q_OBJECT 9 | public: 10 | explicit DateEdit(QWidget *parent = 0); 11 | virtual void stepBy(int steps) Q_DECL_OVERRIDE; 12 | protected: 13 | virtual StepEnabled stepEnabled() const Q_DECL_OVERRIDE; 14 | virtual void focusInEvent(QFocusEvent *event) Q_DECL_OVERRIDE; 15 | signals: 16 | 17 | public slots: 18 | }; 19 | 20 | #endif // DATEEDIT_H 21 | -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 | #include "mainwindow.h" 2 | #include 3 | 4 | int main(int argc, char *argv[]) 5 | { 6 | QApplication a(argc, argv); 7 | a.setApplicationName(QObject::tr("AT Accountning")); 8 | 9 | // command line parsing 10 | QStringList cmdArg = QCoreApplication::arguments(); 11 | QString filePath; 12 | if( cmdArg.length()<=1 ) 13 | filePath = ""; 14 | else 15 | filePath = cmdArg.last(); 16 | 17 | MainWindow w(filePath); 18 | w.initialize(true); 19 | return a.exec(); 20 | } 21 | -------------------------------------------------------------------------------- /ledgeritem.cpp: -------------------------------------------------------------------------------- 1 | #include "ledgeritem.h" 2 | #include "ledger.h" 3 | 4 | bool LedgerItem::operator<(const QStandardItem &other) const 5 | { 6 | if( model() && other.model() && model()==other.model() && 7 | column() == Ledger::COL_DESCRIP && other.column() == Ledger::COL_DESCRIP ) 8 | { 9 | QString a, b; 10 | a = data(model()->sortRole()).toString(); 11 | b = other.data(other.model()->sortRole()).toString(); 12 | return a.localeAwareCompare(b)<0; 13 | } 14 | return QStandardItem::operator <(other); 15 | } 16 | -------------------------------------------------------------------------------- /myfileinfo.h: -------------------------------------------------------------------------------- 1 | #ifndef MYFILEINFO_H 2 | #define MYFILEINFO_H 3 | #include 4 | 5 | class MyFileInfo : public QFileInfo 6 | { 7 | public: 8 | MyFileInfo() { QFileInfo(); newFile = false; } 9 | bool fileOpened() { return !(filePath().isEmpty()); } 10 | bool isNewFile() { return newFile; } 11 | void setNewFile(bool newFile) { this->newFile = newFile; } 12 | QString dirPath(); 13 | static QString dirPath(const QString &file); 14 | int setValidFile(const QString &file); 15 | static int checkValidFile(QString &file); 16 | private: 17 | bool newFile; 18 | }; 19 | 20 | #endif // MYFILEINFO_H 21 | -------------------------------------------------------------------------------- /dateedit.cpp: -------------------------------------------------------------------------------- 1 | #include "dateedit.h" 2 | 3 | DateEdit::DateEdit(QWidget *parent) : QDateEdit(parent) 4 | { 5 | setMinimumDate(QDate(1000,1,1)); 6 | } 7 | 8 | void DateEdit::stepBy(int steps) 9 | { 10 | if( currentSection()==QDateTimeEdit::DaySection ) 11 | setDate(date().addDays(steps)); 12 | else if( currentSection()==QDateTimeEdit::MonthSection ) 13 | setDate(date().addMonths(steps)); 14 | else if( currentSection()==QDateTimeEdit::YearSection ) 15 | setDate(date().addYears(steps)); 16 | } 17 | 18 | QAbstractSpinBox::StepEnabled DateEdit::stepEnabled() const 19 | { 20 | return QAbstractSpinBox::StepUpEnabled|QAbstractSpinBox::StepDownEnabled; 21 | } 22 | 23 | void DateEdit::focusInEvent(QFocusEvent *event) 24 | { 25 | QDateTimeEdit::focusInEvent(event); 26 | setSelectedSection(QDateTimeEdit::DaySection); 27 | } 28 | -------------------------------------------------------------------------------- /Accounting.pro: -------------------------------------------------------------------------------- 1 | #------------------------------------------------- 2 | # 3 | # Project created by QtCreator 2016-09-12T10:34:16 4 | # 5 | #------------------------------------------------- 6 | 7 | QT += core gui 8 | 9 | greaterThan(QT_MAJOR_VERSION, 4): QT += widgets 10 | 11 | TARGET = ATAc 12 | TEMPLATE = app 13 | 14 | 15 | SOURCES += main.cpp\ 16 | mainwindow.cpp \ 17 | commands.cpp \ 18 | dateedit.cpp \ 19 | ledger.cpp \ 20 | ledgerdelegate.cpp \ 21 | ledgeritem.cpp \ 22 | ledgervheader.cpp \ 23 | ledgerview.cpp \ 24 | myfileinfo.cpp 25 | 26 | HEADERS += mainwindow.h \ 27 | commands.h \ 28 | dateedit.h \ 29 | ledger.h \ 30 | ledgerdelegate.h \ 31 | ledgeritem.h \ 32 | ledgervheader.h \ 33 | ledgerview.h \ 34 | myfileinfo.h 35 | 36 | FORMS += mainwindow.ui 37 | 38 | DISTFILES += \ 39 | .gitignore 40 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Windows image file caches 2 | Thumbs.db 3 | ehthumbs.db 4 | 5 | # Folder config file 6 | Desktop.ini 7 | 8 | # Recycle Bin used on file shares 9 | $RECYCLE.BIN/ 10 | 11 | # Windows Installer files 12 | *.cab 13 | *.msi 14 | *.msm 15 | *.msp 16 | 17 | # Windows shortcuts 18 | *.lnk 19 | 20 | # ========================= 21 | # Operating System Files 22 | # ========================= 23 | 24 | # OSX 25 | # ========================= 26 | 27 | .DS_Store 28 | .AppleDouble 29 | .LSOverride 30 | 31 | # Thumbnails 32 | ._* 33 | 34 | # Files that might appear in the root of a volume 35 | .DocumentRevisions-V100 36 | .fseventsd 37 | .Spotlight-V100 38 | .TemporaryItems 39 | .Trashes 40 | .VolumeIcon.icns 41 | 42 | # Directories potentially created on remote AFP share 43 | .AppleDB 44 | .AppleDesktop 45 | Network Trash Folder 46 | Temporary Items 47 | .apdisk 48 | 49 | # custom config 50 | *.csv 51 | *.pro.user 52 | *.bat -------------------------------------------------------------------------------- /ledgervheader.h: -------------------------------------------------------------------------------- 1 | #ifndef LEDGERVHEADER_H 2 | #define LEDGERVHEADER_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include "ledger.h" 8 | 9 | class LedgerVHeader : public QHeaderView 10 | { 11 | Q_OBJECT 12 | public: 13 | explicit LedgerVHeader(); 14 | ~LedgerVHeader(); 15 | virtual void setModel(QAbstractItemModel *model) Q_DECL_OVERRIDE; 16 | protected: 17 | virtual void contextMenuEvent(QContextMenuEvent *event) Q_DECL_OVERRIDE; 18 | virtual void mousePressEvent(QMouseEvent *e) Q_DECL_OVERRIDE; 19 | virtual void mouseDoubleClickEvent(QMouseEvent *e) Q_DECL_OVERRIDE; 20 | virtual void mouseMoveEvent(QMouseEvent *e) Q_DECL_OVERRIDE; 21 | virtual void mouseReleaseEvent(QMouseEvent *e) Q_DECL_OVERRIDE; 22 | private: 23 | int mouseLeftPressIdx, mouseRightPressIdx, mouseRightReleaseIdx, mouseLeftReleaseIdx; 24 | void prepareMoveRow(QMouseEvent *e); 25 | bool moveReady; 26 | QRubberBand *movingRow; 27 | Ledger * ledger; 28 | }; 29 | 30 | #endif // LEDGERVHEADER_H 31 | -------------------------------------------------------------------------------- /ledgerdelegate.h: -------------------------------------------------------------------------------- 1 | #ifndef LEDGERDELEGATE_H 2 | #define LEDGERDELEGATE_H 3 | #include 4 | 5 | class LedgerDelegate : public QStyledItemDelegate 6 | { 7 | Q_OBJECT 8 | public: 9 | LedgerDelegate(QObject *parent = Q_NULLPTR); 10 | QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &, 11 | const QModelIndex &index) const Q_DECL_OVERRIDE; 12 | virtual void setEditorData(QWidget *editor, const QModelIndex &index) const Q_DECL_OVERRIDE; 13 | virtual void setModelData(QWidget *editor, QAbstractItemModel *model, 14 | const QModelIndex &index) const Q_DECL_OVERRIDE; 15 | virtual QString displayText(const QVariant &value, const QLocale &locale) const Q_DECL_OVERRIDE; 16 | virtual void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const Q_DECL_OVERRIDE; 17 | virtual QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const Q_DECL_OVERRIDE; 18 | virtual void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const Q_DECL_OVERRIDE; 19 | }; 20 | 21 | #endif // LEDGERDELEGATE_H 22 | -------------------------------------------------------------------------------- /ledgerview.h: -------------------------------------------------------------------------------- 1 | #ifndef LEDGERVIEW_H 2 | #define LEDGERVIEW_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include "ledger.h" 8 | 9 | class LedgerView : public QTableView 10 | { 11 | Q_OBJECT 12 | public: 13 | explicit LedgerView(QWidget *parent = 0); 14 | void setLedger(Ledger * ledger); 15 | QList rowActions(); 16 | public slots: 17 | void insertRowAbove(); 18 | void insertRowBelow(); 19 | void moveCurrentRowUp(); 20 | void moveCurrentRowDown(); 21 | void deleteCurrentRow(); 22 | void manageActions(); 23 | signals: 24 | void filesDropped(const QList &urls); 25 | protected: 26 | virtual void keyPressEvent(QKeyEvent *event) Q_DECL_OVERRIDE; 27 | virtual void dragEnterEvent(QDragEnterEvent *event) Q_DECL_OVERRIDE; 28 | virtual void dragMoveEvent(QDragMoveEvent *event) Q_DECL_OVERRIDE; 29 | virtual void dropEvent(QDropEvent *event) Q_DECL_OVERRIDE; 30 | protected slots: 31 | virtual void currentChanged(const QModelIndex ¤t, const QModelIndex &previous) Q_DECL_OVERRIDE; 32 | private: 33 | Ledger * ledger; 34 | QAction* actionInsertRowAbove; 35 | QAction* actionInsertRowBelow; 36 | QAction* actionMoveRowUp; 37 | QAction* actionMoveRowDown; 38 | QAction* actionDeleteRow; 39 | }; 40 | 41 | #endif // LEDGERVIEW_H 42 | -------------------------------------------------------------------------------- /ledger.h: -------------------------------------------------------------------------------- 1 | #ifndef LEDGER_H 2 | #define LEDGER_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include "ledgeritem.h" 10 | 11 | class Ledger : public QStandardItemModel 12 | { 13 | Q_OBJECT 14 | public: 15 | static int NCOL, COL_DATE, COL_DESCRIP, COL_PRICE, COL_CATEG; 16 | static QStringList categOptions; 17 | explicit Ledger(QLineEdit *totalBox, QLineEdit *netBox, QLineEdit *debtBox, QLineEdit *loanBox); 18 | QUndoStack *undoStack; 19 | void readFile(QTextStream *inputStream = Q_NULLPTR); 20 | bool isValidFile() { return validFile; } 21 | bool readCsv(QTextStream *stream); 22 | void writeCsv(QTextStream &stream); 23 | bool isEmptyRow(int row); 24 | QList constructEmptyRow(); 25 | virtual void sort(int column, Qt::SortOrder order) Q_DECL_OVERRIDE; 26 | signals: 27 | void ledgerChanged(bool); 28 | private: 29 | double total, net, debt, loan; 30 | QLineEdit *totalBox, *netBox, *debtBox, *loanBox; 31 | bool validFile; 32 | bool checkCsvRow(QList &row, QString &field, QChar ch = 0); 33 | private slots: 34 | void computeTotal(const QModelIndex &index=QModelIndex()); 35 | void sendChangeSignal(bool clean) { emit ledgerChanged(!clean); } 36 | }; 37 | 38 | #endif // LEDGER_H 39 | -------------------------------------------------------------------------------- /mainwindow.h: -------------------------------------------------------------------------------- 1 | #ifndef MAINWINDOW_H 2 | #define MAINWINDOW_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include "ledger.h" 12 | #include "myfileinfo.h" 13 | 14 | namespace Ui { 15 | class MainWindow; 16 | } 17 | 18 | class MainWindow : public QMainWindow 19 | { 20 | Q_OBJECT 21 | 22 | public: 23 | explicit MainWindow(QString filePath = QString(), QWidget *parent = 0); 24 | ~MainWindow(); 25 | void initialize(bool defaultDir = true); 26 | static QFont textFont, numFont; 27 | static QFont defaultTextFont() { QFont font("Calibri",11); font.insertSubstitution("Calibri","Microsoft YaHei"); return font; } 28 | static QFont defaultNumFont() { return QFont("Lucida Console",11); } 29 | public slots: 30 | void filesDropped(const QList &urls); 31 | protected: 32 | virtual void closeEvent(QCloseEvent *event) Q_DECL_OVERRIDE; 33 | private slots: 34 | void on_actionNew_triggered(); 35 | 36 | void on_actionOpen_triggered(); 37 | 38 | bool on_actionSave_triggered(); 39 | 40 | void on_actionSaveAs_triggered(); 41 | 42 | void on_actionClose_triggered(); 43 | 44 | void on_actionExit_triggered() { close(); } 45 | 46 | void setWindowClean(bool clean) { setWindowModified(!clean); } 47 | private: 48 | Ui::MainWindow *ui; 49 | Ledger * ledger; 50 | MyFileInfo currentFile; 51 | QLabel* openingMsg; 52 | void openFile(const QString &filePath); 53 | bool saveFile(const QString &filePath); 54 | void setPath(const QString &filePath); 55 | void setupLedger(QTextStream *inputStream=Q_NULLPTR); 56 | bool confirmSaveClose(); 57 | }; 58 | 59 | #endif // MAINWINDOW_H 60 | -------------------------------------------------------------------------------- /commands.h: -------------------------------------------------------------------------------- 1 | #ifndef COMMANDS_H 2 | #define COMMANDS_H 3 | 4 | #include "ledger.h" 5 | #include 6 | #include 7 | 8 | class DeleteRow : public QUndoCommand { 9 | public: 10 | DeleteRow(int row, Ledger *ledger); 11 | void redo() Q_DECL_OVERRIDE; 12 | void undo() Q_DECL_OVERRIDE; 13 | private: 14 | int deletedRow; 15 | bool onlyRow; 16 | Ledger *ledger; 17 | QList takenRow; 18 | }; 19 | 20 | class MoveRow : public QUndoCommand { 21 | public: 22 | MoveRow(int oldRow, int newRow, Ledger *ledger); 23 | void redo() Q_DECL_OVERRIDE; 24 | void undo() Q_DECL_OVERRIDE; 25 | private: 26 | int oldRow, newRow; 27 | Ledger *ledger; 28 | }; 29 | 30 | 31 | class AddRow : public QUndoCommand { 32 | public: 33 | AddRow(int row, Ledger *ledger); 34 | void redo() Q_DECL_OVERRIDE; 35 | void undo() Q_DECL_OVERRIDE; 36 | private: 37 | int row; 38 | Ledger *ledger; 39 | }; 40 | 41 | class ChangeItem : public QUndoCommand { 42 | public: 43 | ChangeItem(const QVariant &data, const QModelIndex &index, Ledger *ledger, int role=Qt::EditRole); 44 | void redo() Q_DECL_OVERRIDE; 45 | void undo() Q_DECL_OVERRIDE; 46 | private: 47 | QVariant newData, oldData; 48 | int role; 49 | bool lastRow; 50 | QModelIndex index; 51 | Ledger *ledger; 52 | }; 53 | 54 | class DeleteItems : public QUndoCommand { 55 | public: 56 | DeleteItems(const QModelIndexList &indices, Ledger *ledger, int role=Qt::EditRole); 57 | void redo() Q_DECL_OVERRIDE; 58 | void undo() Q_DECL_OVERRIDE; 59 | private: 60 | QModelIndexList indices; 61 | QVector clearedItems; 62 | int role; 63 | Ledger *ledger; 64 | }; 65 | 66 | class SortByColumn : public QUndoCommand { 67 | public: 68 | SortByColumn(int column, Qt::SortOrder order, Ledger *ledger); 69 | void redo() Q_DECL_OVERRIDE; 70 | void undo() Q_DECL_OVERRIDE; 71 | private: 72 | int column; 73 | Qt::SortOrder order; 74 | Ledger *ledger; 75 | bool firstTime; 76 | int nRow; 77 | QVector permut; 78 | }; 79 | 80 | #endif // COMMANDS_H 81 | -------------------------------------------------------------------------------- /myfileinfo.cpp: -------------------------------------------------------------------------------- 1 | #include "myfileinfo.h" 2 | #include 3 | 4 | QString MyFileInfo::dirPath(const QString &file) 5 | { 6 | QString processedFileName = file; 7 | processedFileName.remove( QRegularExpression("^\"") ); 8 | processedFileName.remove( QRegularExpression("\"$") ); 9 | processedFileName.remove( QRegularExpression("\\$") ); 10 | processedFileName.remove( QRegularExpression("/$") ); 11 | 12 | if(!exists(processedFileName)) return QString(""); 13 | QFileInfo tmp(processedFileName); 14 | if(tmp.isDir()) return tmp.absoluteFilePath(); 15 | else return tmp.absolutePath(); 16 | } 17 | 18 | QString MyFileInfo::dirPath() 19 | { 20 | if( filePath().isEmpty() || !exists() ) return QString(""); 21 | if( isDir() ) return absoluteFilePath(); 22 | else return absolutePath(); 23 | } 24 | 25 | int MyFileInfo::setValidFile(const QString &file) 26 | /* return 0 if "file" doesn't exist 27 | return 1 if "file" exist and is a file 28 | return 2 if "file" exist but is not a file (likely a directory) 29 | */ 30 | { 31 | QString processedFileName = file; 32 | processedFileName.remove( QRegularExpression("^\"") ); 33 | processedFileName.remove( QRegularExpression("\"$") ); 34 | processedFileName.remove( QRegularExpression("\\$") ); 35 | processedFileName.remove( QRegularExpression("/$") ); 36 | if(exists(processedFileName)){ 37 | setFile(processedFileName); 38 | if(isFile()) 39 | return 1; 40 | else 41 | return 2; 42 | } else 43 | return 0; 44 | } 45 | 46 | int MyFileInfo::checkValidFile(QString &file) 47 | /* remove any starting/ending quotes and ending slash of "file" 48 | return 0 if "file" doesn't exist 49 | return 1 if "file" exist and is a file 50 | return 2 if "file" exist but is not a file (likely a directory)*/ 51 | { 52 | file.remove( QRegularExpression("^\"") ); 53 | file.remove( QRegularExpression("\"$") ); 54 | file.remove( QRegularExpression("\\$") ); 55 | file.remove( QRegularExpression("/$") ); 56 | if(exists(file)){ 57 | if(QFileInfo(file).isFile()) 58 | return 1; 59 | else 60 | return 2; 61 | } else 62 | return 0; 63 | } 64 | -------------------------------------------------------------------------------- /commands.cpp: -------------------------------------------------------------------------------- 1 | #include "commands.h" 2 | 3 | DeleteRow::DeleteRow(int row, Ledger *ledger) 4 | { 5 | this->deletedRow = row; 6 | this->ledger = ledger; 7 | } 8 | 9 | void DeleteRow::redo() 10 | { 11 | takenRow = ledger->takeRow(deletedRow); 12 | if(ledger->rowCount()==0) { 13 | onlyRow = true; 14 | ledger->appendRow(ledger->constructEmptyRow()); 15 | } else 16 | onlyRow = false; 17 | } 18 | 19 | void DeleteRow::undo() 20 | { 21 | if(onlyRow) 22 | ledger->removeRow(0); 23 | ledger->insertRow(deletedRow,takenRow); 24 | } 25 | 26 | 27 | MoveRow::MoveRow(int oldRow, int newRow, Ledger *ledger) 28 | { 29 | this->oldRow = oldRow; 30 | this->newRow = newRow; 31 | this->ledger = ledger; 32 | } 33 | 34 | void MoveRow::redo() 35 | { 36 | QList takenRow = ledger->takeRow(oldRow); 37 | ledger->insertRow(newRow,takenRow); 38 | } 39 | 40 | void MoveRow::undo() 41 | { 42 | QList takenRow = ledger->takeRow(newRow); 43 | ledger->insertRow(oldRow,takenRow); 44 | } 45 | 46 | 47 | AddRow::AddRow(int row, Ledger *ledger) 48 | { 49 | this->row = row; 50 | this->ledger = ledger; 51 | } 52 | 53 | void AddRow::redo() 54 | { 55 | ledger->insertRow(row,ledger->constructEmptyRow()); 56 | } 57 | 58 | void AddRow::undo() 59 | { 60 | ledger->removeRow(row); 61 | } 62 | 63 | 64 | ChangeItem::ChangeItem(const QVariant &data, const QModelIndex &index, Ledger *ledger, int role) 65 | { 66 | this->newData = data; 67 | this->index = index; 68 | this->ledger = ledger; 69 | this->role = role; 70 | this->oldData = ledger->data(index,role); 71 | lastRow = (index.row()==ledger->rowCount()-1); 72 | } 73 | 74 | void ChangeItem::redo() 75 | { 76 | ledger->setData(index,newData,role); 77 | if(lastRow) 78 | ledger->appendRow(ledger->constructEmptyRow()); 79 | } 80 | 81 | void ChangeItem::undo() 82 | { 83 | if(lastRow) 84 | ledger->removeRow(ledger->rowCount()-1); 85 | ledger->setData(index,oldData,role); 86 | } 87 | 88 | DeleteItems::DeleteItems(const QModelIndexList &indices, Ledger *ledger, int role) 89 | { 90 | this->indices = indices; 91 | this->ledger = ledger; 92 | this->role = role; 93 | clearedItems.reserve(indices.size()); 94 | foreach(const QModelIndex & index, indices) 95 | clearedItems.append(ledger->data(index,role)); 96 | } 97 | 98 | void DeleteItems::redo() 99 | { 100 | foreach(const QModelIndex & index, indices) 101 | ledger->setData(index,QVariant(),role); 102 | } 103 | 104 | void DeleteItems::undo() 105 | { 106 | QModelIndexList::const_iterator i; 107 | QVector::const_iterator j; 108 | i = indices.cbegin(); 109 | j = clearedItems.cbegin(); 110 | while( isetData(*i,*j,role); 112 | i++; 113 | j++; 114 | } 115 | } 116 | 117 | SortByColumn::SortByColumn(int column, Qt::SortOrder order, Ledger *ledger) 118 | { 119 | this->column = column; 120 | this->order = order; 121 | this->ledger = ledger; 122 | firstTime = true; 123 | nRow = ledger->rowCount(); 124 | } 125 | 126 | void SortByColumn::redo() 127 | { 128 | if(firstTime){ 129 | QVector indices; 130 | indices.reserve(nRow); 131 | permut.reserve(nRow); 132 | for( int i = 0; i < nRow; i++) 133 | indices.append(QPersistentModelIndex(ledger->index(i,0))); 134 | ledger->QStandardItemModel::sort(column,order); 135 | foreach(const QPersistentModelIndex & index, indices) 136 | permut.append(index.row()); 137 | firstTime = false; 138 | } else { 139 | QVector > tmpLedger; 140 | tmpLedger.resize(nRow); 141 | for( int i = 0; i < nRow; i++ ) 142 | tmpLedger.replace(permut.at(i),ledger->takeRow(0)); 143 | for( int i = 0; i < nRow; i++ ) 144 | ledger->appendRow(tmpLedger.at(i)); 145 | } 146 | } 147 | 148 | void SortByColumn::undo() 149 | { 150 | QVector > tmpLedger; 151 | tmpLedger.reserve(nRow); 152 | for( int i = 0; i < nRow; i++ ) 153 | tmpLedger.append(ledger->takeRow(0)); 154 | for( int i = 0; i < nRow; i++ ) 155 | ledger->appendRow(tmpLedger.at(permut.at(i))); 156 | } 157 | -------------------------------------------------------------------------------- /ledgervheader.cpp: -------------------------------------------------------------------------------- 1 | #include "ledgervheader.h" 2 | #include "ledgerview.h" 3 | #include "commands.h" 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | LedgerVHeader::LedgerVHeader() : QHeaderView(Qt::Vertical) 10 | { 11 | setSectionsClickable(true); 12 | moveReady = false; 13 | mouseLeftPressIdx = -1; 14 | mouseRightPressIdx = -1; 15 | mouseRightReleaseIdx = -1; 16 | mouseLeftReleaseIdx = -1; 17 | movingRow = Q_NULLPTR; 18 | } 19 | 20 | LedgerVHeader::~LedgerVHeader() 21 | { 22 | if(movingRow) delete movingRow; 23 | } 24 | 25 | void LedgerVHeader::setModel(QAbstractItemModel *model) 26 | { 27 | QHeaderView::setModel(model); 28 | ledger = static_cast(model); 29 | } 30 | 31 | void LedgerVHeader::contextMenuEvent(QContextMenuEvent *event) 32 | { 33 | if(event->reason()==QContextMenuEvent::Mouse){ 34 | mouseRightReleaseIdx = logicalIndexAt(event->pos()); 35 | if( mouseRightReleaseIdx<0 || mouseRightReleaseIdx>=model()->rowCount()) return; 36 | QMenu menu(this); 37 | LedgerView* view = static_cast(parent()); 38 | menu.addAction("Insert Row &Above",view, &LedgerView::insertRowAbove); 39 | menu.addAction("Insert Row &Below",view, &LedgerView::insertRowBelow); 40 | if(mouseRightReleaseIdx>0) 41 | menu.addAction("Move Row U&p", view, &LedgerView::moveCurrentRowUp); 42 | if(mouseRightReleaseIdxrowCount()-1) 43 | menu.addAction("Move Row D&own",view, &LedgerView::moveCurrentRowDown); 44 | if( model()->rowCount()>1 || !ledger->isEmptyRow(mouseRightReleaseIdx) ) 45 | menu.addAction( "De&lete Row", view, &LedgerView::deleteCurrentRow); 46 | menu.exec(event->globalPos()); 47 | } 48 | QHeaderView::contextMenuEvent(event); 49 | } 50 | 51 | void LedgerVHeader::mousePressEvent(QMouseEvent *e) 52 | { 53 | if(e->button()==Qt::LeftButton){ 54 | prepareMoveRow(e); 55 | } else if(e->button()==Qt::RightButton) 56 | mouseRightPressIdx = logicalIndexAt(e->pos()); 57 | QHeaderView::mousePressEvent(e); 58 | } 59 | 60 | void LedgerVHeader::mouseDoubleClickEvent(QMouseEvent *e) 61 | { 62 | QHeaderView::mouseDoubleClickEvent(e); 63 | prepareMoveRow(e); 64 | } 65 | 66 | void LedgerVHeader::mouseMoveEvent(QMouseEvent *e) 67 | { 68 | if(moveReady && (e->buttons() & Qt::LeftButton)==Qt::LeftButton){ 69 | if(movingRow) 70 | movingRow->move(0,pos().y()+e->pos().y()-movingRow->height()/2); 71 | //e->ignore(); 72 | } 73 | else 74 | QHeaderView::mouseMoveEvent(e); 75 | } 76 | 77 | void LedgerVHeader::mouseReleaseEvent(QMouseEvent *e) 78 | { 79 | if(e->button()==Qt::RightButton) { 80 | mouseRightReleaseIdx = logicalIndexAt(e->pos()); 81 | LedgerView *view = static_cast(parent()); 82 | view->selectRow(mouseRightReleaseIdx); 83 | view->selectionModel()->setCurrentIndex(model()->index(mouseRightReleaseIdx,0),QItemSelectionModel::Current); 84 | } else if(e->button()==Qt::LeftButton) { 85 | if(movingRow){ 86 | movingRow->hide(); 87 | unsetCursor(); 88 | } 89 | QPoint mouseLeftReleasePos = e->pos(); 90 | mouseLeftReleaseIdx = logicalIndexAt(mouseLeftReleasePos); 91 | int newRow = mouseLeftReleaseIdx; 92 | int oldRow = mouseLeftPressIdx; 93 | if(moveReady && 94 | newRow != oldRow && 95 | newRow >= 0 && oldRow < model()->rowCount() && 96 | newRow >= 0 && oldRow < model()->rowCount() ){ 97 | int top = sectionViewportPosition(newRow); 98 | int height = sectionSize(newRow); 99 | bool upper = (mouseLeftReleasePos.y()-top) < height/2 ; 100 | LedgerView *view = static_cast(parent()); 101 | if(newRowoldRow && upper) newRow--; 103 | ledger->undoStack->push(new MoveRow(oldRow,newRow,ledger)); 104 | view->selectRow(newRow); 105 | } 106 | } 107 | QHeaderView::mouseReleaseEvent(e); 108 | } 109 | 110 | void LedgerVHeader::prepareMoveRow(QMouseEvent *e) 111 | { 112 | mouseLeftPressIdx = logicalIndexAt(e->pos()); 113 | LedgerView *view = static_cast(parent()); 114 | QModelIndexList selectedIdxes = view->selectionModel()->selectedRows(); 115 | moveReady = (selectedIdxes.size()==1 && selectedIdxes.at(0).row()==mouseLeftPressIdx); 116 | if(moveReady){ 117 | int height = sectionSize(mouseLeftPressIdx); 118 | if(!movingRow) 119 | movingRow = new QRubberBand(QRubberBand::Rectangle,view); 120 | movingRow->setGeometry(0,pos().y()+e->pos().y()-height/2,view->width(),height); 121 | movingRow->show(); 122 | setCursor(QCursor(Qt::ClosedHandCursor)); 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /ledgerdelegate.cpp: -------------------------------------------------------------------------------- 1 | #include "ledgerdelegate.h" 2 | #include "ledger.h" 3 | #include "commands.h" 4 | #include "dateedit.h" 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | LedgerDelegate::LedgerDelegate(QObject *parent) : QStyledItemDelegate(parent){} 14 | 15 | QWidget *LedgerDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const 16 | { 17 | if(index.column()==Ledger::COL_DATE){ 18 | DateEdit *dateEditor = new DateEdit(parent); 19 | dateEditor->setCalendarPopup(true); 20 | dateEditor->setDisplayFormat("yyyy-MM-dd"); 21 | dateEditor->setFont( index.model()->data(index,Qt::FontRole).value() ); 22 | dateEditor->setAlignment(Qt::AlignHCenter); 23 | dateEditor->calendarWidget()->setGridVisible(true); 24 | return dateEditor; 25 | } else if(index.column()==Ledger::COL_DESCRIP) { 26 | QLineEdit *textEditor = new QLineEdit(parent); 27 | QStringList descripList; 28 | int start = std::max(index.row()-100,0); 29 | int end = std::min(index.row()+101,index.model()->rowCount()); 30 | for(int i = start; i < end; i++){ 31 | QString itemString = index.model()-> 32 | data( index.sibling(i,Ledger::COL_DESCRIP),Qt::EditRole ).toString(); 33 | if(!descripList.contains(itemString)) 34 | descripList.append(itemString); 35 | } 36 | QCompleter *completer = new QCompleter(descripList,textEditor); 37 | completer->setCaseSensitivity(Qt::CaseInsensitive); 38 | completer->setFilterMode(Qt::MatchContains); 39 | textEditor->setFont(option.font); 40 | textEditor->setCompleter(completer); 41 | return textEditor; 42 | } else if(index.column()==Ledger::COL_PRICE){ 43 | QDoubleSpinBox *numberEditor = new QDoubleSpinBox(parent); 44 | numberEditor->setSingleStep(1.0); 45 | numberEditor->setDecimals(2); 46 | numberEditor->setMinimum(std::numeric_limits::lowest()); 47 | numberEditor->setMaximum(std::numeric_limits::max()); 48 | numberEditor->setAlignment(Qt::AlignRight); 49 | numberEditor->setGroupSeparatorShown(true); 50 | numberEditor->setFont( index.model()->data(index,Qt::FontRole).value() ); 51 | return numberEditor; 52 | } else if(index.column()==Ledger::COL_CATEG){ 53 | QComboBox *categEditor = new QComboBox(parent); 54 | categEditor->insertItems(0,Ledger::categOptions); 55 | return categEditor; 56 | } else 57 | return QStyledItemDelegate::createEditor(parent,option,index); 58 | } 59 | 60 | void LedgerDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const 61 | { 62 | if(index.column()==Ledger::COL_DATE){ 63 | DateEdit *dateEditor = static_cast(editor); 64 | QDate date = index.model()->data(index,Qt::EditRole).toDate(); 65 | if(date.isNull()) 66 | dateEditor->setDate(QDate::currentDate()); 67 | else 68 | dateEditor->setDate( date ); 69 | } else if(index.column()==Ledger::COL_DESCRIP) { 70 | QLineEdit *textEditor = static_cast(editor); 71 | textEditor->setText(index.model()->data(index,Qt::EditRole).toString()); 72 | } else if(index.column()==Ledger::COL_PRICE){ 73 | QDoubleSpinBox *numberEditor = static_cast(editor); 74 | double value = index.model()->data(index,Qt::EditRole).toDouble(); 75 | if(qIsNaN(value)) 76 | numberEditor->setValue( 0.0 ); 77 | else 78 | numberEditor->setValue( value ); 79 | } else if(index.column()==Ledger::COL_CATEG){ 80 | QComboBox *categEditor = static_cast(editor); 81 | int categ = index.model()->data(index,Qt::EditRole).toInt(); 82 | categEditor->setCurrentIndex(categ); 83 | } 84 | } 85 | 86 | void LedgerDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const 87 | { 88 | Ledger *ledger = static_cast(model); 89 | QVariant newData; 90 | if(index.column()==Ledger::COL_DATE){ 91 | DateEdit *dateEditor = static_cast(editor); 92 | dateEditor->interpretText(); 93 | newData.setValue(dateEditor->date()); 94 | } else if(index.column()==Ledger::COL_DESCRIP) { 95 | QLineEdit *textEditor = static_cast(editor); 96 | QString text = textEditor->text(); 97 | if(!text.isEmpty()) 98 | newData.setValue(text); 99 | } else if(index.column()==Ledger::COL_PRICE){ 100 | QDoubleSpinBox *numberEditor = static_cast(editor); 101 | numberEditor->interpretText(); 102 | newData.setValue(numberEditor->value()); 103 | } else if(index.column()==Ledger::COL_CATEG){ 104 | QComboBox *categEditor = static_cast(editor); 105 | newData.setValue(categEditor->currentIndex()); 106 | } 107 | if(ledger->data(index,Qt::EditRole)!=newData) 108 | ledger->undoStack->push(new ChangeItem(newData,index,ledger)); 109 | } 110 | 111 | QString LedgerDelegate::displayText(const QVariant &value, const QLocale &locale) const 112 | { 113 | if(value.isNull()) return ""; 114 | if(QMetaType::Type(value.type())==QMetaType::Double) { 115 | double number = value.toDouble(); 116 | if(qIsNaN(number)) 117 | return ""; 118 | else { 119 | QString numStr = QLocale().toString(number,'f',2); 120 | numStr += " "; 121 | return numStr; 122 | } 123 | } else if(QMetaType::Type(value.type())==QMetaType::QDate) 124 | return value.toDate().toString("yyyy-MM-dd"); 125 | else if(QMetaType::Type(value.type())==QMetaType::Int) { 126 | int categ=value.toInt(); 127 | if(categ >= 0 && categ < Ledger::categOptions.length()) 128 | return Ledger::categOptions.at(categ); 129 | else 130 | return ""; 131 | } else 132 | return QStyledItemDelegate::displayText(value,locale); 133 | } 134 | 135 | void LedgerDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const 136 | { 137 | QStyledItemDelegate::paint(painter,option,index); 138 | if(index.column()!=Ledger::COL_PRICE) return; 139 | painter->save(); 140 | painter->setFont(index.model()->data(index,Qt::FontRole).value()); 141 | painter->drawText(option.rect.adjusted(3,0,0,0),Qt::AlignLeft|Qt::AlignVCenter,QLocale().currencySymbol()); 142 | painter->restore(); 143 | } 144 | 145 | QSize LedgerDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const 146 | { 147 | if(index.column()==Ledger::COL_PRICE){ 148 | QFontMetrics metrics(index.model()->data(index,Qt::FontRole).value()); 149 | double number = index.model()->data(index,Qt::EditRole).toDouble(); 150 | QString numStr = "$ " + QLocale().toString(number,'f',2) + " "; 151 | int width = metrics.width(numStr)+6; 152 | int height = metrics.height(); 153 | return QSize(width,height); 154 | } 155 | else 156 | return QStyledItemDelegate::sizeHint(option,index); 157 | } 158 | 159 | void LedgerDelegate::updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const 160 | { 161 | QStyledItemDelegate::updateEditorGeometry(editor,option,index); 162 | } 163 | -------------------------------------------------------------------------------- /mainwindow.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | MainWindow 4 | 5 | 6 | 7 | 0 8 | 0 9 | 800 10 | 600 11 | 12 | 13 | 14 | MainWindow 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 11 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 0 34 | 0 35 | 36 | 37 | 38 | 39 | 16777215 40 | 16777215 41 | 42 | 43 | 44 | 45 | Segoe UI 46 | 11 47 | 48 | 49 | 50 | Total: $ 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 0 59 | 0 60 | 61 | 62 | 63 | 64 | 120 65 | 16777215 66 | 67 | 68 | 69 | 70 | Lucida Console 71 | 11 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | Segoe UI 81 | 11 82 | 83 | 84 | 85 | = Net: $ 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 120 94 | 16777215 95 | 96 | 97 | 98 | 99 | Lucida Console 100 | 11 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | Segoe UI 110 | 11 111 | 112 | 113 | 114 | + Debt: $ 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 120 123 | 16777215 124 | 125 | 126 | 127 | 128 | Lucida Console 129 | 11 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | Segoe UI 139 | 11 140 | 141 | 142 | 143 | - Loan: $ 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 120 152 | 16777215 153 | 154 | 155 | 156 | 157 | Lucida Console 158 | 11 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | Qt::Horizontal 167 | 168 | 169 | 170 | 40 171 | 20 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 0 184 | 0 185 | 800 186 | 21 187 | 188 | 189 | 190 | 191 | &File 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | &Edit 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | &Open 212 | 213 | 214 | 215 | 216 | Save &As 217 | 218 | 219 | 220 | 221 | E&xit 222 | 223 | 224 | 225 | 226 | &Save 227 | 228 | 229 | 230 | 231 | &New 232 | 233 | 234 | 235 | 236 | &Close 237 | 238 | 239 | Ctrl+W 240 | 241 | 242 | 243 | 244 | 245 | 246 | LedgerView 247 | QTableView 248 |
ledgerview.h
249 |
250 |
251 | 252 | 253 |
254 | -------------------------------------------------------------------------------- /ledgerview.cpp: -------------------------------------------------------------------------------- 1 | #include "ledgerview.h" 2 | #include "ledgerdelegate.h" 3 | #include "ledgervheader.h" 4 | #include "commands.h" 5 | #include 6 | #include 7 | #include 8 | 9 | LedgerView::LedgerView(QWidget *parent) : QTableView(parent) 10 | { 11 | horizontalHeader()->setSectionsMovable(true); 12 | setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel); 13 | setVerticalHeader(new LedgerVHeader); 14 | QAbstractItemDelegate* d = itemDelegate(); 15 | setItemDelegate(new LedgerDelegate); 16 | delete d; 17 | setSortingEnabled(true); 18 | 19 | setAcceptDrops(true); 20 | 21 | actionInsertRowAbove = new QAction(tr("Insert Empty Row &Above"),this); 22 | actionInsertRowBelow = new QAction(tr("Insert Empty Row &Below"),this); 23 | actionMoveRowUp = new QAction(tr("Move Current Row U&p"), this); 24 | actionMoveRowDown = new QAction(tr("Move Current Row &Down"), this); 25 | actionDeleteRow = new QAction(tr("De&lete Current Row"), this); 26 | 27 | actionInsertRowAbove->setShortcut(QKeySequence::fromString("Ctrl+Alt+Up")); 28 | actionInsertRowBelow->setShortcut(QKeySequence::fromString("Ctrl+Alt+Down")); 29 | actionMoveRowUp ->setShortcut(QKeySequence::fromString("Ctrl+Shift+Up")); 30 | actionMoveRowDown ->setShortcut(QKeySequence::fromString("Ctrl+Shift+Down")); 31 | actionDeleteRow ->setShortcut(QKeySequence::fromString("Ctrl+Delete")); 32 | 33 | actionInsertRowAbove->setShortcutContext(Qt::WindowShortcut); 34 | actionInsertRowBelow->setShortcutContext(Qt::WindowShortcut); 35 | actionMoveRowUp ->setShortcutContext(Qt::WindowShortcut); 36 | actionMoveRowDown ->setShortcutContext(Qt::WindowShortcut); 37 | actionDeleteRow ->setShortcutContext(Qt::WindowShortcut); 38 | 39 | connect(actionInsertRowAbove,&QAction::triggered, this,&LedgerView::insertRowAbove); 40 | connect(actionInsertRowBelow,&QAction::triggered, this,&LedgerView::insertRowBelow); 41 | connect(actionMoveRowUp ,&QAction::triggered, this,&LedgerView::moveCurrentRowUp); 42 | connect(actionMoveRowDown ,&QAction::triggered, this,&LedgerView::moveCurrentRowDown); 43 | connect(actionDeleteRow ,&QAction::triggered, this,&LedgerView::deleteCurrentRow); 44 | } 45 | 46 | void LedgerView::setLedger(Ledger * ledger) 47 | { 48 | this->ledger = ledger; 49 | QItemSelectionModel* m = selectionModel(); 50 | setModel(ledger); 51 | delete m; 52 | if( ledger && ledger->isValidFile() && ledger->columnCount()==Ledger::NCOL ){ 53 | setColumnWidth(Ledger::COL_DATE, 120); 54 | setColumnWidth(Ledger::COL_DESCRIP,200); 55 | setColumnWidth(Ledger::COL_PRICE, 125); 56 | setColumnWidth(Ledger::COL_CATEG, 110); 57 | setEditTriggers(DoubleClicked|EditKeyPressed|AnyKeyPressed); 58 | } else{ 59 | resizeColumnsToContents(); 60 | setEditTriggers(NoEditTriggers); 61 | } 62 | 63 | manageActions();\ 64 | if(ledger){ 65 | connect(ledger,&QAbstractItemModel::rowsRemoved, this,&LedgerView::manageActions); 66 | connect(ledger,&QAbstractItemModel::rowsInserted, this,&LedgerView::manageActions); 67 | connect(ledger,&QAbstractItemModel::rowsMoved, this,&LedgerView::manageActions); 68 | connect(ledger,&QAbstractItemModel::layoutChanged,this,&LedgerView::manageActions); 69 | connect(ledger,&QObject::destroyed, this,&LedgerView::manageActions); 70 | } 71 | } 72 | 73 | QList LedgerView::rowActions() 74 | { 75 | QList actions; 76 | actions << actionInsertRowAbove; 77 | actions << actionInsertRowBelow; 78 | actions << actionMoveRowUp; 79 | actions << actionMoveRowDown; 80 | actions << actionDeleteRow; 81 | return actions; 82 | } 83 | 84 | void LedgerView::insertRowAbove() 85 | { 86 | if( !model() || !currentIndex().isValid() ) return; 87 | int currentRow = currentIndex().row(); 88 | ledger->undoStack->push(new AddRow(currentRow,ledger)); 89 | } 90 | 91 | void LedgerView::insertRowBelow() 92 | { 93 | if( !model() || !currentIndex().isValid() ) return; 94 | int currentRow = currentIndex().row(); 95 | ledger->undoStack->push(new AddRow(currentRow+1,ledger)); 96 | } 97 | 98 | void LedgerView::moveCurrentRowUp() 99 | { 100 | if( !model() || !currentIndex().isValid() ) return; 101 | int currentRow = currentIndex().row(); 102 | int currentCol = currentIndex().column(); 103 | if( currentRow <= 0 ) return; 104 | ledger->undoStack->push(new MoveRow(currentRow,currentRow-1,ledger)); 105 | QModelIndex index = model()->index(currentRow-1,currentCol); 106 | setCurrentIndex(index); 107 | selectionModel()->select(index,QItemSelectionModel::ClearAndSelect|QItemSelectionModel::Rows); 108 | } 109 | 110 | void LedgerView::moveCurrentRowDown() 111 | { 112 | if( !model() || !currentIndex().isValid() ) return; 113 | int currentRow = currentIndex().row(); 114 | int currentCol = currentIndex().column(); 115 | if( currentRow >= model()->rowCount()-1 ) return; 116 | ledger->undoStack->push(new MoveRow(currentRow,currentRow+1,ledger)); 117 | QModelIndex index = model()->index(currentRow+1,currentCol); 118 | setCurrentIndex(index); 119 | selectionModel()->select(index,QItemSelectionModel::ClearAndSelect|QItemSelectionModel::Rows); 120 | } 121 | 122 | void LedgerView::deleteCurrentRow() 123 | { 124 | if( !model() || !currentIndex().isValid() ) return; 125 | int currentRow = currentIndex().row(); 126 | int currentCol = currentIndex().column(); 127 | QAbstractItemModel* mod = model(); 128 | if( mod->rowCount()==1 && ledger->isEmptyRow(currentRow) ) return; 129 | ledger->undoStack->push(new DeleteRow(currentRow,ledger)); 130 | QModelIndex index; 131 | if( currentRow < mod->rowCount() ) 132 | index = mod->index(currentRow,currentCol); 133 | else if( currentRow > 0 ) 134 | index = mod->index(currentRow-1,currentCol); 135 | selectionModel()->setCurrentIndex(index,QItemSelectionModel::Current); 136 | } 137 | 138 | void LedgerView::currentChanged(const QModelIndex ¤t, const QModelIndex &previous) 139 | { 140 | QAbstractItemView::currentChanged(current,previous); 141 | manageActions(); 142 | } 143 | 144 | void LedgerView::manageActions() 145 | { 146 | if(!currentIndex().isValid() || !model()){ 147 | actionInsertRowAbove->setEnabled(false); 148 | actionInsertRowBelow->setEnabled(false); 149 | actionMoveRowUp ->setEnabled(false); 150 | actionMoveRowDown ->setEnabled(false); 151 | actionDeleteRow ->setEnabled(false); 152 | return; 153 | } 154 | actionInsertRowAbove->setEnabled(true); 155 | actionInsertRowBelow->setEnabled(true); 156 | 157 | if(currentIndex().row()==0) 158 | actionMoveRowUp->setEnabled(false); 159 | else 160 | actionMoveRowUp->setEnabled(true); 161 | if(currentIndex().row()==model()->rowCount()-1) 162 | actionMoveRowDown->setEnabled(false); 163 | else 164 | actionMoveRowDown->setEnabled(true); 165 | 166 | if( model()->rowCount()==1 && ledger->isEmptyRow(currentIndex().row()) ) 167 | actionDeleteRow->setEnabled(false); 168 | else 169 | actionDeleteRow->setEnabled(true); 170 | } 171 | 172 | void LedgerView::keyPressEvent(QKeyEvent *event) 173 | { 174 | if( event->key()==Qt::Key_Delete && (event->modifiers()&(~Qt::KeypadModifier))==0 ) { 175 | QModelIndexList selected = selectedIndexes(); 176 | bool empty = true; 177 | foreach (const QModelIndex & index, selected) 178 | if(!model()->data(index,Qt::EditRole).isNull()) 179 | empty = false; 180 | if(!empty) 181 | ledger->undoStack->push(new DeleteItems(selected,ledger)); 182 | } 183 | QAbstractItemView::keyPressEvent(event); 184 | } 185 | 186 | void LedgerView::dragEnterEvent(QDragEnterEvent *event) 187 | { 188 | if(event->mimeData()->hasUrls() && event->proposedAction()==Qt::CopyAction) 189 | event->accept(); 190 | else 191 | QTableView::dragEnterEvent(event); 192 | } 193 | 194 | void LedgerView::dragMoveEvent(QDragMoveEvent *event) 195 | { 196 | if(event->mimeData()->hasUrls() && event->proposedAction()==Qt::CopyAction) 197 | event->acceptProposedAction(); 198 | else 199 | QTableView::dragMoveEvent(event); 200 | } 201 | 202 | void LedgerView::dropEvent(QDropEvent *event) 203 | { 204 | if(event->mimeData()->hasUrls() && event->proposedAction()==Qt::CopyAction){ 205 | QList urls = event->mimeData()->urls(); 206 | emit filesDropped(urls); 207 | } else 208 | QTableView::dropEvent(event); 209 | } 210 | -------------------------------------------------------------------------------- /ledger.cpp: -------------------------------------------------------------------------------- 1 | #include "ledger.h" 2 | #include "commands.h" 3 | #include "mainwindow.h" 4 | #include 5 | #include 6 | #include 7 | 8 | int Ledger::COL_DATE = 0; 9 | int Ledger::COL_DESCRIP = 1; 10 | int Ledger::COL_PRICE = 2; 11 | int Ledger::COL_CATEG = 3; 12 | int Ledger::NCOL = 4; 13 | QStringList Ledger::categOptions = (QStringList() 14 | << "Spending" << "Income" << "Debt" << "Loan" << "Off the book"); 15 | 16 | Ledger::Ledger(QLineEdit *totalBox, QLineEdit *netBox, QLineEdit *debtBox, QLineEdit *loanBox) : QStandardItemModel() 17 | { 18 | // variable initialization 19 | total = 0.0; 20 | debt = 0.0; 21 | loan = 0.0; 22 | net = total - debt + loan; 23 | // setup header 24 | QStringList header; 25 | header << "Date" << "Description" << "Amount" << "Category"; 26 | setHorizontalHeaderLabels(header); 27 | // set display box 28 | this->totalBox = totalBox; 29 | this->netBox = netBox; 30 | this->debtBox = debtBox; 31 | this->loanBox = loanBox; 32 | // setup undo framework 33 | undoStack = new QUndoStack(this); 34 | } 35 | 36 | void Ledger::readFile(QTextStream *inputStream) 37 | { 38 | if(inputStream){ 39 | if( !(validFile = readCsv(inputStream)) ){ 40 | clear(); 41 | setHorizontalHeaderLabels(QStringList("Error")); 42 | appendRow(static_cast(new LedgerItem::QStandardItem("Invalid File Format"))); 43 | } 44 | computeTotal(); 45 | } else{ 46 | appendRow(constructEmptyRow()); 47 | validFile = true; 48 | } 49 | undoStack->setClean(); 50 | // setup signals 51 | connect(this,&QAbstractItemModel::dataChanged, this,&computeTotal); 52 | connect(this,&QAbstractItemModel::rowsRemoved, this,&computeTotal); 53 | connect(this,&QAbstractItemModel::rowsInserted,this,&computeTotal); 54 | connect(undoStack,&QUndoStack::cleanChanged, this,&sendChangeSignal); 55 | } 56 | 57 | bool Ledger::readCsv(QTextStream *stream) 58 | { 59 | QChar ch; 60 | QString field; 61 | QList row; 62 | while (!stream->atEnd()) { 63 | *stream >> ch; 64 | if (ch == ',' || ch == '\n'){ 65 | if(!checkCsvRow(row, field, ch)) 66 | return false; 67 | } else if (stream->atEnd()) { 68 | field.append(ch); 69 | if(!checkCsvRow(row, field)) 70 | return false; 71 | } else 72 | field.append(ch); 73 | } 74 | appendRow(constructEmptyRow()); 75 | return true; 76 | } 77 | 78 | bool Ledger::checkCsvRow(QList &row, QString &field, QChar ch) 79 | { 80 | if(field.count("\"")%2 == 0) { 81 | if (field.startsWith( QChar('\"')) && field.endsWith( QChar('\"') ) ) { 82 | field.remove( QRegularExpression("^\"") ); 83 | field.remove( QRegularExpression("\"$") ); 84 | } 85 | field.replace("\"\"", "\""); 86 | LedgerItem *item = new LedgerItem(); 87 | if( row.size()==COL_DATE ){ 88 | QDate fieldDate = QDate::fromString(field,Qt::ISODate); 89 | item->setData(QVariant(fieldDate),Qt::EditRole); 90 | item->setData(Qt::AlignCenter,Qt::TextAlignmentRole); 91 | item->setData(MainWindow::numFont,Qt::FontRole); 92 | } else if( row.size()==COL_DESCRIP ) { 93 | item->setData(QVariant(field),Qt::EditRole); 94 | item->setData(int(Qt::AlignLeft|Qt::AlignVCenter),Qt::TextAlignmentRole); 95 | } else if( row.size()==COL_PRICE ) { 96 | bool numeric; 97 | double fieldNum = field.toDouble(&numeric); 98 | if(numeric && fieldNum!=qQNaN()) 99 | item->setData(QVariant(fieldNum),Qt::EditRole); 100 | item->setData(int(Qt::AlignRight|Qt::AlignVCenter),Qt::TextAlignmentRole); 101 | item->setData(MainWindow::numFont,Qt::FontRole); 102 | } else if( row.size()==COL_CATEG ) { 103 | bool isInt; 104 | int fieldInt = field.toInt(&isInt); 105 | if(isInt) 106 | item->setData(QVariant(fieldInt),Qt::EditRole); 107 | item->setData(int(Qt::AlignLeft|Qt::AlignVCenter),Qt::TextAlignmentRole); 108 | } else 109 | item->setText(field); // Not really necessary 110 | if(row.size()(item)); 112 | field.clear(); 113 | if (ch != QChar(',')) { 114 | QCoreApplication::processEvents(QEventLoop::AllEvents,1); 115 | if(row.size()!=NCOL) return false; 116 | appendRow(row); 117 | row.clear(); 118 | } 119 | } else { 120 | field.append(ch); 121 | } 122 | return true; 123 | } 124 | 125 | void Ledger::writeCsv(QTextStream &stream) 126 | { 127 | int i, j; 128 | QString field; 129 | for( i=0; idata(Qt::EditRole).isNull() ) 134 | field = ""; 135 | else if( j==COL_DATE ) 136 | field = item(i,j)->data(Qt::EditRole).toDate().toString(Qt::ISODate); 137 | else if( j==COL_PRICE ) 138 | field.setNum(item(i,j)->data(Qt::EditRole).toDouble(),'f',2); 139 | else{ 140 | field = item(i,j)->data(Qt::EditRole).toString(); 141 | if( field.contains( QRegularExpression("[\",\n]") ) ){ 142 | field.replace("\"", "\"\""); 143 | field.prepend(QChar('\"')); 144 | field.append(QChar('\"')); 145 | } 146 | } 147 | stream << field; 148 | if(j==NCOL-1) stream << '\n'; 149 | else stream << ','; 150 | } 151 | } 152 | stream.flush(); 153 | undoStack->setClean(); 154 | } 155 | 156 | void Ledger::computeTotal(const QModelIndex &index) 157 | { 158 | if( !validFile || (index.isValid() && (index.column()!=COL_PRICE && index.column()!=COL_CATEG)) ) return; 159 | double itemPrice; 160 | int itemCateg; 161 | total = 0.0; 162 | debt = 0.0; 163 | loan = 0.0; 164 | net = 0.0; 165 | for( int i=0; idata(Qt::EditRole).toDouble(); 167 | itemCateg = item(i,COL_CATEG)->data(Qt::EditRole).toInt(); 168 | if(!qIsNaN(itemPrice)){ 169 | if(itemCateg==0) 170 | total -= itemPrice; 171 | else if(itemCateg==1) 172 | total += itemPrice; 173 | else if(itemCateg==2){ 174 | total += itemPrice; 175 | debt += itemPrice; 176 | } else if(itemCateg==3){ 177 | total -= itemPrice; 178 | loan += itemPrice; 179 | } 180 | } 181 | } 182 | net = total - debt + loan; 183 | totalBox->setText( QLocale().toString(total,'f',2) ); 184 | netBox ->setText( QLocale().toString(net, 'f',2) ); 185 | debtBox ->setText( QLocale().toString(debt, 'f',2) ); 186 | loanBox ->setText( QLocale().toString(loan, 'f',2) ); 187 | } 188 | 189 | QList Ledger::constructEmptyRow() 190 | { 191 | QList row; 192 | LedgerItem *item; 193 | for(int i=0; isetData(Qt::AlignCenter,Qt::TextAlignmentRole); 197 | item->setData(MainWindow::numFont,Qt::FontRole); 198 | } else if(i==COL_DESCRIP){ 199 | item = new LedgerItem; 200 | item->setData(int(Qt::AlignLeft|Qt::AlignVCenter),Qt::TextAlignmentRole); 201 | } else if(i==COL_PRICE){ 202 | item = new LedgerItem; 203 | item->setData(int(Qt::AlignRight|Qt::AlignVCenter),Qt::TextAlignmentRole); 204 | item->setData(MainWindow::numFont,Qt::FontRole); 205 | } else if(i==COL_CATEG){ 206 | item = new LedgerItem; 207 | item->setData(int(Qt::AlignLeft|Qt::AlignVCenter),Qt::TextAlignmentRole); 208 | } else 209 | item = new LedgerItem; // not really necessary 210 | row.append(static_cast(item)); 211 | } 212 | return row; 213 | } 214 | 215 | void Ledger::sort(int column, Qt::SortOrder order) 216 | { 217 | undoStack->push(new SortByColumn(column,order,this)); 218 | } 219 | 220 | bool Ledger::isEmptyRow(int row) 221 | { 222 | return item(row,COL_DATE ) ->data(Qt::EditRole).isNull() && 223 | item(row,COL_DESCRIP)->data(Qt::EditRole).isNull() && 224 | item(row,COL_PRICE ) ->data(Qt::EditRole).isNull() && 225 | item(row,COL_CATEG ) ->data(Qt::EditRole).isNull(); 226 | } 227 | -------------------------------------------------------------------------------- /mainwindow.cpp: -------------------------------------------------------------------------------- 1 | #include "mainwindow.h" 2 | #include "ui_mainwindow.h" 3 | #include "ledger.h" 4 | #include "ledgerview.h" 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | QFont MainWindow::textFont = MainWindow::defaultTextFont(); 17 | QFont MainWindow:: numFont = MainWindow::defaultNumFont(); 18 | 19 | MainWindow::MainWindow(QString filePath, QWidget *parent) : QMainWindow(parent) 20 | { 21 | // setup ui 22 | ui = (new Ui::MainWindow); 23 | ui->setupUi(this); 24 | ui->centralWidget->setFont(textFont); 25 | 26 | // setup ledgerView 27 | ui->ledgerView->setLedger(Q_NULLPTR); 28 | connect(ui->ledgerView,&LedgerView::filesDropped,this,&MainWindow::filesDropped); 29 | 30 | openingMsg = new QLabel(tr("Opening File..."),ui->ledgerView); 31 | QFont font = QApplication::font(); font.setPointSize(14); 32 | openingMsg->setFont(font); 33 | openingMsg->setAlignment(Qt::AlignCenter); 34 | openingMsg->adjustSize(); 35 | QGridLayout* layout = new QGridLayout; 36 | layout->addWidget(openingMsg); 37 | delete ui->ledgerView->layout(); 38 | ui->ledgerView->setLayout(layout); 39 | 40 | ui->totalBox->setReadOnly(true); 41 | ui->netBox ->setReadOnly(true); 42 | ui->debtBox ->setReadOnly(true); 43 | ui->loanBox ->setReadOnly(true); 44 | ui->totalBox->setAlignment(Qt::AlignRight|Qt::AlignVCenter); 45 | ui->netBox ->setAlignment(Qt::AlignRight|Qt::AlignVCenter); 46 | ui->debtBox ->setAlignment(Qt::AlignRight|Qt::AlignVCenter); 47 | ui->loanBox ->setAlignment(Qt::AlignRight|Qt::AlignVCenter); 48 | ui->totalBox->setFont(numFont); 49 | ui->netBox ->setFont(numFont); 50 | ui->debtBox ->setFont(numFont); 51 | ui->loanBox ->setFont(numFont); 52 | 53 | // setup Actions 54 | ui->actionNew->setShortcut(QKeySequence::New); 55 | ui->actionOpen->setShortcut(QKeySequence::Open); 56 | ui->actionSave->setShortcut(QKeySequence::Save); 57 | ui->actionSaveAs->setShortcut(QKeySequence::SaveAs); 58 | ui->actionExit->setShortcut(QKeySequence::Quit); 59 | ui->actionClose->setEnabled(false); 60 | ui->actionSave->setEnabled(false); 61 | ui->actionSaveAs->setEnabled(false); 62 | setWindowModified(false); 63 | 64 | // set window 65 | setWindowTitle(qApp->applicationName()); 66 | // set file path 67 | currentFile.setFile(filePath); 68 | } 69 | 70 | MainWindow::~MainWindow() { delete ui; } 71 | 72 | void MainWindow::initialize(bool defaultDir) 73 | { 74 | show(); 75 | 76 | ledger = Q_NULLPTR; 77 | if( currentFile.isDir() ){ 78 | QDir::setCurrent(currentFile.absoluteFilePath()); 79 | currentFile.setFile(""); 80 | on_actionOpen_triggered(); 81 | } else if( currentFile.fileOpened() ) 82 | openFile(currentFile.absoluteFilePath()); 83 | else { 84 | if( defaultDir ){ 85 | QStringList docPaths = QStandardPaths::standardLocations(QStandardPaths::DocumentsLocation); 86 | int i = 0; 87 | while( i &urls) 97 | { 98 | foreach (const QUrl & url, urls) { 99 | QString filePath = url.toLocalFile(); 100 | if(QFileInfo(filePath).isDir()){ 101 | QDir::setCurrent(filePath); 102 | on_actionOpen_triggered(); 103 | } else 104 | openFile(filePath); 105 | } 106 | } 107 | 108 | void MainWindow::openFile(const QString &filePath) 109 | { 110 | if(filePath.isEmpty()) return; 111 | QFile inputFile(filePath); 112 | if(inputFile.open(QIODevice::ReadOnly | QIODevice::Text)){ 113 | if( ledger && ledger->isValidFile() && !(currentFile.isNewFile() && !isWindowModified()) ){ 114 | MainWindow* w = new MainWindow(filePath); 115 | int offset = std::abs(geometry().y() - frameGeometry().y()); 116 | w->move( pos().x()+offset, pos().y()+offset ); 117 | if(!qApp->desktop()->availableGeometry(this).contains(w->frameGeometry())) 118 | w->move(qApp->desktop()->availableGeometry(this).topLeft()); 119 | w->initialize(false); 120 | return; 121 | } 122 | setWindowTitle(tr("Opening \"")+QFileInfo(filePath).fileName()+"\"... - "+qApp->applicationName()); 123 | QTextStream inputStream(&inputFile); 124 | inputStream.setCodec("UTF-8"); 125 | inputStream.setAutoDetectUnicode(true); 126 | ui->actionNew->setEnabled(false); 127 | ui->actionOpen->setEnabled(false); 128 | ui->actionSave->setEnabled(false); 129 | ui->actionSaveAs->setEnabled(false); 130 | ui->actionClose->setEnabled(false); 131 | setupLedger(&inputStream); 132 | inputFile.close(); 133 | 134 | setPath(filePath); 135 | setWindowModified(false); 136 | ui->actionNew->setEnabled(true); 137 | ui->actionOpen->setEnabled(true); 138 | ui->actionSave->setEnabled(false); 139 | ui->actionSaveAs->setEnabled( ledger->isValidFile() ); 140 | ui->actionClose->setEnabled(true); 141 | currentFile.setNewFile(false); 142 | } 143 | else{ 144 | QMessageBox::warning(this, "", tr("Could not open file")); 145 | } 146 | } 147 | 148 | /* Save file at "filePath" 149 | return true if successfully saved 150 | return false if not saved */ 151 | bool MainWindow::saveFile(const QString &filePath) 152 | { 153 | if(filePath.isEmpty()) return false; 154 | QFile outputFile(filePath); 155 | if (outputFile.open(QIODevice::WriteOnly | QIODevice::Text)) { 156 | setWindowTitle(QFileInfo(filePath).fileName()+"[*] - "+qApp->applicationName()+tr(" - Saving...")); 157 | QTextStream outputStream(&outputFile); 158 | outputStream.setCodec("UTF-8"); 159 | ledger->writeCsv(outputStream); 160 | outputFile.close(); 161 | 162 | setPath(filePath); 163 | ui->actionSaveAs->setEnabled(true); 164 | ui->actionClose->setEnabled(true); 165 | currentFile.setNewFile(false); 166 | return true; 167 | } else{ 168 | QMessageBox::critical(this, tr("Error"), tr("Could not save file")); 169 | return false; 170 | } 171 | } 172 | 173 | // input "filePath" must be VALID file path 174 | void MainWindow::setPath(const QString & filePath) 175 | { 176 | currentFile.setValidFile(filePath); 177 | QDir::setCurrent(currentFile.dirPath()); 178 | setWindowTitle(currentFile.fileName()+"[*] - "+qApp->applicationName()); 179 | } 180 | 181 | void MainWindow::setupLedger(QTextStream * inputStream) 182 | { 183 | delete ledger; 184 | ledger = (new Ledger(ui->totalBox,ui->netBox,ui->debtBox,ui->loanBox)); 185 | QAction* undoAction = ledger->undoStack->createUndoAction(ui->menu_Edit,tr("&Undo")); 186 | QAction* redoAction = ledger->undoStack->createRedoAction(ui->menu_Edit,tr("&Redo")); 187 | ui->totalBox->setText(""); 188 | ui->netBox ->setText(""); 189 | ui->debtBox ->setText(""); 190 | ui->loanBox ->setText(""); 191 | ui->menu_Edit->clear(); 192 | undoAction->setShortcut(QKeySequence::Undo); 193 | redoAction->setShortcut(QKeySequence::Redo); 194 | ui->menu_Edit->addAction(undoAction); 195 | ui->menu_Edit->addAction(redoAction); 196 | ui->menu_Edit->addSeparator(); 197 | ui->menu_Edit->addActions(ui->ledgerView->rowActions()); 198 | 199 | openingMsg->show(); 200 | ledger->readFile(inputStream); 201 | openingMsg->hide(); 202 | ui->ledgerView->setLedger(ledger); 203 | 204 | connect(ledger,&Ledger::ledgerChanged,this,&setWindowModified); 205 | connect(ledger,&Ledger::ledgerChanged,ui->actionSave,&QAction::setEnabled); 206 | } 207 | 208 | bool MainWindow::confirmSaveClose() 209 | { 210 | if(isWindowModified()){ 211 | QMessageBox::StandardButton button = 212 | QMessageBox::question(this,tr("Save"),QString(tr("Save file ")+currentFile.fileName()+tr("?")), 213 | QMessageBox::StandardButtons(QMessageBox::Yes|QMessageBox::No|QMessageBox::Cancel),QMessageBox::Yes); 214 | if(button==QMessageBox::Yes) 215 | return on_actionSave_triggered(); 216 | else if(button==QMessageBox::No) 217 | return true; 218 | else 219 | return false; 220 | } 221 | return true; 222 | } 223 | 224 | void MainWindow::on_actionNew_triggered() 225 | { 226 | if(ledger && ledger->isValidFile()){ 227 | MainWindow* w = new MainWindow(); 228 | int offset = std::abs(geometry().y() - frameGeometry().y()); 229 | w->move( pos().x()+offset, pos().y()+offset ); 230 | if(!qApp->desktop()->availableGeometry(this).contains(w->frameGeometry())) 231 | w->move(qApp->desktop()->availableGeometry(this).topLeft()); 232 | w->initialize(false); 233 | return; 234 | } 235 | currentFile.setFile(QDir::current(),tr("New Sheet")+".csv"); 236 | setWindowTitle(currentFile.fileName()+"[*] - "+qApp->applicationName()); 237 | setupLedger(); 238 | ui->actionClose->setEnabled(true); 239 | ui->actionSave->setEnabled(false); 240 | ui->actionSaveAs->setEnabled(true); 241 | setWindowModified(false); 242 | currentFile.setNewFile(true); 243 | } 244 | 245 | void MainWindow::on_actionOpen_triggered() 246 | { 247 | QString filePath = QFileDialog::getOpenFileName 248 | (this,QString(),QDir::currentPath(),tr("CSV Files")+" (*.csv);;"+tr("All Files")+" (*.*)",0,QFileDialog::DontResolveSymlinks); 249 | openFile(filePath); 250 | } 251 | 252 | bool MainWindow::on_actionSave_triggered() 253 | { 254 | if(currentFile.isNewFile()){ 255 | QString filePath = QFileDialog::getSaveFileName 256 | (this,QString(), currentFile.absoluteFilePath(),tr("CSV Files")+" (*.csv);;"+tr("All Files")+" (*.*)",0,QFileDialog::DontResolveSymlinks); 257 | return saveFile(filePath); 258 | } 259 | else return saveFile(currentFile.absoluteFilePath()); 260 | } 261 | 262 | void MainWindow::on_actionSaveAs_triggered() 263 | { 264 | QString filePath = QFileDialog::getSaveFileName 265 | (this,QString(), QDir::currentPath(),tr("CSV Files")+" (*.csv)",0,QFileDialog::DontResolveSymlinks); 266 | saveFile(filePath); 267 | } 268 | 269 | void MainWindow::on_actionClose_triggered() 270 | { 271 | if(!confirmSaveClose()) return; 272 | currentFile.setFile(""); 273 | setWindowTitle(qApp->applicationName()); 274 | ui->ledgerView->setLedger(Q_NULLPTR); 275 | delete ledger; ledger = Q_NULLPTR; 276 | ui->totalBox->setText(""); 277 | ui->netBox ->setText(""); 278 | ui->debtBox ->setText(""); 279 | ui->loanBox ->setText(""); 280 | ui->actionClose->setEnabled(false); 281 | ui->actionSave->setEnabled(false); 282 | ui->actionSaveAs->setEnabled(false); 283 | setWindowModified(false); 284 | } 285 | 286 | void MainWindow::closeEvent(QCloseEvent *event) 287 | { 288 | if(confirmSaveClose()) 289 | event->accept(); 290 | else 291 | event->ignore(); 292 | } 293 | 294 | --------------------------------------------------------------------------------