tailswidget.cpp 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294
  1. /*
  2. *openPilotLog - A FOSS Pilot Logbook Application
  3. *Copyright (C) 2020-2023 Felix Turowsky
  4. *
  5. *This program is free software: you can redistribute it and/or modify
  6. *it under the terms of the GNU General Public License as published by
  7. *the Free Software Foundation, either version 3 of the License, or
  8. *(at your option) any later version.
  9. *
  10. *This program is distributed in the hope that it will be useful,
  11. *but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. *MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. *GNU General Public License for more details.
  14. *
  15. *You should have received a copy of the GNU General Public License
  16. *along with this program. If not, see <https://www.gnu.org/licenses/>.
  17. */
  18. #include "tailswidget.h"
  19. #include "ui_aircraftwidget.h"
  20. #include "src/opl.h"
  21. #include "src/classes/settings.h"
  22. #include "src/database/database.h"
  23. #include "src/database/row.h"
  24. #include "src/gui/dialogues/newtaildialog.h"
  25. TailsWidget::TailsWidget(QWidget *parent) :
  26. QWidget(parent),
  27. ui(new Ui::AircraftWidget)
  28. {
  29. ui->setupUi(this);
  30. ui->tableView->setMinimumWidth(this->width()/2);
  31. ui->stackedWidget->setMinimumWidth(this->width()/2);
  32. setupModelAndView();
  33. }
  34. TailsWidget::~TailsWidget()
  35. {
  36. delete ui;
  37. }
  38. void TailsWidget::setupModelAndView()
  39. {
  40. model = new QSqlTableModel(this);
  41. model->setTable(QStringLiteral("viewTails"));
  42. model->select();
  43. view = ui->tableView;
  44. view->setModel(model);
  45. view->setSelectionBehavior(QAbstractItemView::SelectRows);
  46. view->setSelectionMode(QAbstractItemView::SingleSelection); // For now, editing multiple entries is not supported.
  47. view->setEditTriggers(QAbstractItemView::NoEditTriggers);
  48. view->horizontalHeader()->setStretchLastSection(QHeaderView::Stretch);
  49. view->hideColumn(0);
  50. view->resizeColumnsToContents();
  51. view->verticalHeader()->hide();
  52. view->setAlternatingRowColors(true);
  53. sortColumn = Settings::read(Settings::UserData::TailSortColumn).toInt();
  54. view->setSortingEnabled(true);
  55. view->sortByColumn(sortColumn, Qt::DescendingOrder);
  56. view->show();
  57. selection = view->selectionModel();
  58. connectSignalsAndSlots();
  59. }
  60. void TailsWidget::connectSignalsAndSlots()
  61. {
  62. QObject::connect(ui->tableView->selectionModel(), &QItemSelectionModel::selectionChanged,
  63. this, &TailsWidget::tableView_selectionChanged);
  64. QObject::connect(ui->tableView->horizontalHeader(), &QHeaderView::sectionClicked,
  65. this, &TailsWidget::tableView_headerClicked);
  66. }
  67. /*
  68. * Slots
  69. */
  70. void TailsWidget::onAircraftWidget_settingChanged(SettingsWidget::SettingSignal signal)
  71. {
  72. if (signal != SettingsWidget::AircraftWidget)
  73. return;
  74. setupModelAndView();
  75. }
  76. void TailsWidget::onAircraftWidget_dataBaseUpdated()
  77. {
  78. refreshView();
  79. }
  80. void TailsWidget::changeEvent(QEvent *event)
  81. {
  82. if (event != nullptr)
  83. if(event->type() == QEvent::LanguageChange)
  84. ui->retranslateUi(this);
  85. }
  86. void TailsWidget::on_newAircraftButton_clicked()
  87. {
  88. NewTailDialog nt(QString(), this);
  89. setUiEnabled(false);
  90. nt.exec();
  91. refreshView();
  92. setUiEnabled(true);
  93. }
  94. /*!
  95. * \brief Displays a dialog in which the Tail can be edited
  96. */
  97. void TailsWidget::tableView_selectionChanged()
  98. {
  99. if (this->findChild<NewTailDialog*>() != nullptr)
  100. delete this->findChild<NewTailDialog*>();
  101. selectedTails.clear();
  102. const auto selected_rows = selection->selectedRows();
  103. for (const auto& row : selected_rows) {
  104. selectedTails << row.data().toInt();
  105. DEB << "Selected Tails(s) with ID: " << selectedTails;
  106. }
  107. if(selectedTails.length() == 1) {
  108. NewTailDialog nt(selectedTails.first(), this);
  109. nt.setWindowFlag(Qt::Widget);
  110. ui->stackedWidget->addWidget(&nt);
  111. ui->stackedWidget->setCurrentWidget(&nt);
  112. setUiEnabled(false);
  113. nt.exec();
  114. refreshView();
  115. setUiEnabled(true);
  116. }
  117. }
  118. /*!
  119. * \brief Acts as a filter on the display model
  120. */
  121. void TailsWidget::on_aircraftSearchLineEdit_textChanged(const QString &arg1)
  122. {
  123. if(ui->aircraftSearchComboBox->currentIndex() == 0){
  124. ui->aircraftSearchLineEdit->setText(arg1.toUpper());
  125. }
  126. model->setFilter(ui->aircraftSearchComboBox->currentText()
  127. + QLatin1String(" LIKE \"%")
  128. + arg1 + QLatin1String("%\""));
  129. }
  130. void TailsWidget::tableView_headerClicked(int column)
  131. {
  132. sortColumn = column;
  133. Settings::write(Settings::UserData::TailSortColumn, column);
  134. }
  135. void TailsWidget::on_deleteAircraftButton_clicked()
  136. {
  137. if (selectedTails.length() == 0) {
  138. INFO(tr("No Aircraft selected."));
  139. } else if (selectedTails.length() > 1) {
  140. WARN(tr("Deleting multiple entries is currently not supported"));
  141. /// [F] to do: for (const auto& row_id : selectedPilots) { do batchDelete }
  142. /// I am not sure if enabling this functionality for this widget is a good idea.
  143. /// On the one hand, deleting many entries could be useful in a scenario where
  144. /// for example, the user has changed airlines and does not want to have his 'old'
  145. /// colleagues polluting his logbook anymore.
  146. /// On the other hand we could run into issues with foreign key constraints on the
  147. /// flights table (see on_delete_unsuccessful) below.
  148. /// I think batch-editing should be implemented at some point, but batch-deleting should not.
  149. } else if (selectedTails.length() == 1) {
  150. auto entry = DB->getTailEntry(selectedTails.first());
  151. QMessageBox message_box(this);
  152. message_box.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
  153. message_box.setDefaultButton(QMessageBox::No);
  154. message_box.setIcon(QMessageBox::Question);
  155. message_box.setWindowTitle(tr("Delete Aircraft"));
  156. message_box.setText(tr("You are deleting the following aircraft:<br><br><b><tt>"
  157. "%1 - (%2)</b></tt><br><br>Are you sure?"
  158. ).arg(entry.getData().value(OPL::Db::TAILS_REGISTRATION).toString(),
  159. getAircraftTypeString(entry)));
  160. if (message_box.exec() == QMessageBox::Yes) {
  161. if(!DB->remove(entry))
  162. onDeleteUnsuccessful();
  163. }
  164. }
  165. refreshView();
  166. ui->stackedWidget->setCurrentIndex(0);
  167. ui->aircraftSearchLineEdit->setText(QString());
  168. }
  169. /*!
  170. * \brief Informs the user that deleting a database entry has been unsuccessful
  171. *
  172. * \details Normally, when one of these entries can not be deleted, it is because of
  173. * a [foreign key constraint](https://sqlite.org/foreignkeys.html), meaning that a flight
  174. * is associated with the aircraft that was supposed to be deleted.
  175. *
  176. * This function is used to inform the user and give hints on how to solve the problem.
  177. */
  178. void TailsWidget::onDeleteUnsuccessful()
  179. {
  180. QList<int> foreign_key_constraints = DB->getForeignKeyConstraints(selectedTails.first(),
  181. OPL::DbTable::Tails);
  182. QList<OPL::FlightEntry> constrained_flights;
  183. for (const auto &row_id : qAsConst(foreign_key_constraints)) {
  184. constrained_flights.append(DB->getFlightEntry(row_id));
  185. }
  186. QMessageBox message_box(this);
  187. if (constrained_flights.isEmpty()) {
  188. message_box.setText(tr("<br>Unable to delete.<br><br>The following error has ocurred: %1"
  189. ).arg(DB->lastError.text()));
  190. message_box.exec();
  191. return;
  192. } else {
  193. QString constrained_flights_string;
  194. for (int i=0; i<constrained_flights.length(); i++) {
  195. constrained_flights_string.append(getFlightSummary(constrained_flights[i]) + QLatin1String("&nbsp;&nbsp;&nbsp;&nbsp;<br>"));
  196. if (i>10) {
  197. constrained_flights_string.append(QLatin1String("<br>[...]<br>"));
  198. break;
  199. }
  200. }
  201. message_box.setText(tr("Unable to delete.<br><br>"
  202. "This is most likely the case because a flight exists with the aircraft "
  203. "you are trying to delete.<br><br>"
  204. "%1 flight(s) with this aircraft have been found:<br><br><br><b><tt>"
  205. "%2"
  206. "</b></tt><br><br>You have to change or remove the conflicting flight(s) "
  207. "before removing this aircraft from the database.<br><br>"
  208. ).arg(QString::number(constrained_flights.length()), constrained_flights_string));
  209. message_box.setIcon(QMessageBox::Critical);
  210. message_box.exec();
  211. }
  212. }
  213. void TailsWidget::setUiEnabled(bool enabled)
  214. {
  215. ui->newAircraftButton->setEnabled(enabled);
  216. ui->deleteAircraftButton->setEnabled(enabled);
  217. ui->tableView->setEnabled(enabled);
  218. ui->aircraftSearchComboBox->setEnabled(enabled);
  219. ui->aircraftSearchComboBox->setEnabled(enabled);
  220. }
  221. void TailsWidget::repopulateModel()
  222. {
  223. // unset the current model and delete it to avoid leak
  224. view->setModel(nullptr);
  225. delete model;
  226. // create a new model and populate it
  227. model = new QSqlTableModel(this);
  228. setupModelAndView();
  229. connectSignalsAndSlots();
  230. }
  231. const QString TailsWidget::getAircraftTypeString(const OPL::Row &row) const
  232. {
  233. QString type_string;
  234. if (!row.getData().value(OPL::Db::TAILS_MAKE).toString().isEmpty())
  235. type_string.append(row.getData().value(OPL::Db::TAILS_MAKE).toString() + QLatin1Char(' '));
  236. if (!row.getData().value(OPL::Db::TAILS_MODEL).toString().isEmpty())
  237. type_string.append(row.getData().value(OPL::Db::TAILS_MODEL).toString());
  238. if (!row.getData().value(OPL::Db::TAILS_VARIANT).toString().isEmpty())
  239. type_string.append(QLatin1Char('-') + row.getData().value(OPL::Db::TAILS_VARIANT).toString());
  240. return type_string;
  241. }
  242. const QString TailsWidget::getFlightSummary(const OPL::FlightEntry &flight) const
  243. {
  244. if(!flight.isValid())
  245. return QString();
  246. auto tableData = flight.getData();
  247. QString flight_summary;
  248. auto space = QLatin1Char(' ');
  249. flight_summary.append(tableData.value(OPL::Db::FLIGHTS_DOFT).toString() + space);
  250. flight_summary.append(tableData.value(OPL::Db::FLIGHTS_DEPT).toString() + space);
  251. flight_summary.append(OPL::Time(tableData.value(OPL::Db::FLIGHTS_TOFB).toInt()).toString()
  252. + space);
  253. flight_summary.append(OPL::Time(tableData.value(OPL::Db::FLIGHTS_TONB).toInt()).toString()
  254. + space);
  255. flight_summary.append(tableData.value(OPL::Db::FLIGHTS_DEST).toString());
  256. return flight_summary;
  257. }