File indexing completed on 2024-05-12 05:09:49

0001 /***************************************************************************
0002     Copyright (C) 2003-2009 Robby Stephenson <robby@periapsis.org>
0003  ***************************************************************************/
0004 
0005 /***************************************************************************
0006  *                                                                         *
0007  *   This program is free software; you can redistribute it and/or         *
0008  *   modify it under the terms of the GNU General Public License as        *
0009  *   published by the Free Software Foundation; either version 2 of        *
0010  *   the License or (at your option) version 3 or any later version        *
0011  *   accepted by the membership of KDE e.V. (or its successor approved     *
0012  *   by the membership of KDE e.V.), which shall act as a proxy            *
0013  *   defined in Section 14 of version 3 of the license.                    *
0014  *                                                                         *
0015  *   This program is distributed in the hope that it will be useful,       *
0016  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
0017  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
0018  *   GNU General Public License for more details.                          *
0019  *                                                                         *
0020  *   You should have received a copy of the GNU General Public License     *
0021  *   along with this program.  If not, see <http://www.gnu.org/licenses/>. *
0022  *                                                                         *
0023  ***************************************************************************/
0024 
0025 #include "tablefieldwidget.h"
0026 #include "../field.h"
0027 #include "../fieldformat.h"
0028 #include "../utils/string_utils.h"
0029 #include "../tellico_debug.h"
0030 
0031 #include <KLocalizedString>
0032 
0033 #include <QMenu>
0034 #include <QTableWidget>
0035 #include <QMouseEvent>
0036 #include <QEvent>
0037 #include <QHeaderView>
0038 #include <QIcon>
0039 #include <QInputDialog>
0040 
0041 namespace {
0042   static const int MIN_TABLE_ROWS = 5;
0043   static const int MAX_TABLE_COLS = 10;
0044 }
0045 
0046 using Tellico::GUI::TableFieldWidget;
0047 
0048 TableFieldWidget::TableFieldWidget(Tellico::Data::FieldPtr field_, QWidget* parent_)
0049     : FieldWidget(field_, parent_), m_row(-1), m_col(-1) {
0050 
0051   bool ok;
0052   m_columns = Tellico::toUInt(field_->property(QStringLiteral("columns")), &ok);
0053   if(!ok) {
0054     m_columns = 1;
0055   } else {
0056     m_columns = qMin(m_columns, MAX_TABLE_COLS);
0057   }
0058 
0059   m_table = new QTableWidget(MIN_TABLE_ROWS, m_columns, this);
0060   labelColumns(field());
0061 
0062   m_table->setDragEnabled(false);
0063 
0064   m_table->horizontalHeader()->setSectionResizeMode(m_columns-1, QHeaderView::Interactive);
0065   m_table->resizeColumnToContents(m_columns-1);
0066   m_table->setSelectionMode(QAbstractItemView::NoSelection);
0067 //  m_table->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
0068 
0069   // capture all the context menu events
0070   m_table->setContextMenuPolicy(Qt::CustomContextMenu);
0071   m_table->verticalHeader()->setContextMenuPolicy(Qt::CustomContextMenu);
0072   m_table->horizontalHeader()->setContextMenuPolicy(Qt::CustomContextMenu);
0073 
0074   connect(m_table, &QTableWidget::itemChanged, this, &TableFieldWidget::checkModified);
0075   connect(m_table, &QTableWidget::itemChanged, this, &TableFieldWidget::slotResizeColumn);
0076   connect(m_table, &QTableWidget::currentCellChanged, this, &TableFieldWidget::slotCheckRows);
0077   connect(m_table, &QWidget::customContextMenuRequested, this, &TableFieldWidget::tableContextMenu);
0078   connect(m_table->horizontalHeader(), &QWidget::customContextMenuRequested, this, &TableFieldWidget::horizontalHeaderContextMenu);
0079   connect(m_table->verticalHeader(), &QWidget::customContextMenuRequested, this, &TableFieldWidget::verticalHeaderContextMenu);
0080 
0081   registerWidget();
0082 }
0083 
0084 QString TableFieldWidget::text() const {
0085   QString text, str, rstack, cstack, rowStr;
0086   for(int row = 0; row < m_table->rowCount(); ++row) {
0087     rowStr.clear();
0088     cstack.clear();
0089     for(int col = 0; col < m_table->columnCount(); ++col) {
0090       QTableWidgetItem* item = m_table->item(row, col);
0091       str = item ? item->text().simplified() : QString();
0092       if(str.isEmpty()) {
0093         cstack += FieldFormat::columnDelimiterString();
0094       } else {
0095         rowStr += cstack + str + FieldFormat::columnDelimiterString();
0096         cstack.clear();
0097       }
0098     }
0099     if(rowStr.isEmpty()) {
0100       rstack += FieldFormat::rowDelimiterString();
0101     } else {
0102       rowStr.truncate(rowStr.length()-FieldFormat::columnDelimiterString().length()); // remove last delimiter
0103       text += rstack + rowStr + FieldFormat::rowDelimiterString();
0104       rstack.clear();
0105     }
0106   }
0107   if(!text.isEmpty()) {
0108     text.truncate(text.length()-FieldFormat::rowDelimiterString().length()); // remove last delimiter
0109   }
0110   return text;
0111 }
0112 
0113 void TableFieldWidget::setTextImpl(const QString& text_) {
0114   const QStringList rows = FieldFormat::splitTable(text_);
0115   if(rows.count() != m_table->rowCount()) {
0116     m_table->setRowCount(qMax(rows.count(), MIN_TABLE_ROWS));
0117   }
0118   for(int row = 0; row < rows.count(); ++row) {
0119     QStringList columnValues = FieldFormat::splitRow(rows.at(row));
0120     const int ncols = m_table->columnCount();
0121     if(ncols < columnValues.count()) {
0122       // need to combine all the last values, from ncols-1 to end
0123       QString lastValue = QStringList(columnValues.mid(ncols-1)).join(FieldFormat::columnDelimiterString());
0124       columnValues = columnValues.mid(0, ncols);
0125       columnValues.replace(ncols-1, lastValue);
0126     }
0127     for(int col = 0; col < ncols; ++col) {
0128       QString value = col < columnValues.count() ? columnValues.at(col) : QString();
0129       QTableWidgetItem* item = new QTableWidgetItem(value);
0130       m_table->setItem(row, col, item);
0131     }
0132   }
0133   // adjust all columns
0134   for(int col = 0; col < m_table->columnCount(); ++col) {
0135     m_table->resizeColumnToContents(col);
0136   }
0137 }
0138 
0139 void TableFieldWidget::clearImpl() {
0140   m_table->clear();
0141   m_table->setRowCount(MIN_TABLE_ROWS);
0142   labelColumns(field());
0143   editMultiple(false);
0144   checkModified();
0145 }
0146 
0147 QWidget* TableFieldWidget::widget() {
0148   return m_table;
0149 }
0150 
0151 void TableFieldWidget::slotCheckRows(int row_, int) {
0152   if(row_ == m_table->rowCount()-1 && !emptyRow(row_)) { // if is last row and row above is not empty
0153     m_table->insertRow(m_table->rowCount());
0154   }
0155 }
0156 
0157 void TableFieldWidget::slotResizeColumn(QTableWidgetItem* item_) {
0158   m_table->resizeColumnToContents(item_->column());
0159 }
0160 
0161 void TableFieldWidget::slotRenameColumn() {
0162   if(m_col < 0 || m_col >= m_columns) {
0163     return;
0164   }
0165   QString name = m_table->horizontalHeaderItem(m_col)->text();
0166   bool ok;
0167   QString newName = QInputDialog::getText(this, i18n("Rename Column"), i18n("New column name:"),
0168                                           QLineEdit::Normal, name, &ok);
0169   if(ok) {
0170     renameColumn(newName);
0171   }
0172 }
0173 
0174 bool TableFieldWidget::emptyRow(int row_) const {
0175   for(int col = 0; col < m_table->columnCount(); ++col) {
0176     QTableWidgetItem* item = m_table->item(row_, col);
0177     if(item && !item->text().isEmpty()) {
0178       return false;
0179     }
0180   }
0181   return true;
0182 }
0183 
0184 void TableFieldWidget::labelColumns(Tellico::Data::FieldPtr field_) {
0185   QStringList labels;
0186   for(int col = 0; col < m_columns; ++col) {
0187     QString s = field_->property(QStringLiteral("column%1").arg(col+1));
0188     if(s.isEmpty()) {
0189       s = i18n("Column %1", col+1);
0190     }
0191     labels += s;
0192   }
0193   m_table->setHorizontalHeaderLabels(labels);
0194 }
0195 
0196 void TableFieldWidget::renameColumn(const QString& newName_) {
0197   Q_ASSERT(m_col >= 0);
0198   Q_ASSERT(m_col < m_columns);
0199   Q_ASSERT(!newName_.isEmpty());
0200 
0201   Data::FieldPtr newField(new Data::Field(*field()));
0202   newField->setProperty(QStringLiteral("column%1").arg(m_col+1), newName_);
0203   emit fieldChanged(newField);
0204   setField(newField);
0205   labelColumns(newField);
0206 }
0207 
0208 void TableFieldWidget::updateFieldHook(Tellico::Data::FieldPtr, Tellico::Data::FieldPtr newField_) {
0209   bool ok;
0210   m_columns = Tellico::toUInt(newField_->property(QStringLiteral("columns")), &ok);
0211   if(!ok) {
0212     m_columns = 1;
0213   } else {
0214     m_columns = qMin(m_columns, MAX_TABLE_COLS); // max of 5 columns
0215   }
0216   if(m_columns != m_table->columnCount()) {
0217     m_table->setColumnCount(m_columns);
0218   }
0219   labelColumns(newField_);
0220 }
0221 
0222 void TableFieldWidget::tableContextMenu(QPoint point_) {
0223   if(point_.isNull()) {
0224     return;
0225   }
0226   m_row = m_table->rowAt(point_.y());
0227   m_col = m_table->columnAt(point_.x());
0228   makeRowContextMenu(m_table->mapToGlobal(point_));
0229 }
0230 
0231 void TableFieldWidget::horizontalHeaderContextMenu(QPoint point_) {
0232   int col = m_table->horizontalHeader()->logicalIndexAt(point_.x());
0233   if(col < 0 || col >= m_columns) {
0234     return;
0235   }
0236   m_row = -1;
0237   m_col = col;
0238 
0239   QMenu menu(this);
0240   menu.addAction(QIcon::fromTheme(QStringLiteral("edit-rename")), i18n("Rename Column..."),
0241                  this, &TableFieldWidget::slotRenameColumn);
0242   menu.addAction(QIcon::fromTheme(QStringLiteral("edit-clear")), i18n("Clear Table"),
0243                  this, &TableFieldWidget::clearImpl);
0244   menu.exec(m_table->horizontalHeader()->mapToGlobal(point_));
0245 }
0246 
0247 void TableFieldWidget::verticalHeaderContextMenu(QPoint point_) {
0248   int row = m_table->verticalHeader()->logicalIndexAt(point_.y());
0249   if(row < 0 || row >= m_table->rowCount()) {
0250     return;
0251   }
0252   m_row = row;
0253   m_col = -1;
0254   makeRowContextMenu(m_table->verticalHeader()->mapToGlobal(point_));
0255  }
0256 
0257 void TableFieldWidget::makeRowContextMenu(QPoint point_) {
0258   QMenu menu(this);
0259   menu.addAction(QIcon::fromTheme(QStringLiteral("edit-table-insert-row-below")), i18n("Insert Row"),
0260                  this, &TableFieldWidget::slotInsertRow);
0261   menu.addAction(QIcon::fromTheme(QStringLiteral("edit-table-delete-row")), i18n("Remove Row"),
0262                  this, &TableFieldWidget::slotRemoveRow);
0263   QAction* act = menu.addAction(QIcon::fromTheme(QStringLiteral("arrow-up")), i18n("Move Row Up"),
0264                                 this, &TableFieldWidget::slotMoveRowUp);
0265   if(m_row < 1) {
0266     act->setEnabled(false);
0267   }
0268   act = menu.addAction(QIcon::fromTheme(QStringLiteral("arrow-down")), i18n("Move Row Down"),
0269                        this, &TableFieldWidget::slotMoveRowDown);
0270   if(m_row < 0 || m_row > m_table->rowCount()-1) {
0271     act->setEnabled(false);
0272   }
0273   menu.addSeparator();
0274   act = menu.addAction(QIcon::fromTheme(QStringLiteral("edit-rename")), i18n("Rename Column..."),
0275                        this, &TableFieldWidget::slotRenameColumn);
0276   if(m_col < 0 || m_col > m_columns-1) {
0277     act->setEnabled(false);
0278   }
0279   menu.addSeparator();
0280   menu.addAction(QIcon::fromTheme(QStringLiteral("edit-clear")), i18n("Clear Table"),
0281                  this, &TableFieldWidget::slotClear);
0282 
0283   menu.exec(point_);
0284 }
0285 
0286 void TableFieldWidget::slotInsertRow() {
0287   if(m_row > -1) {
0288     m_table->insertRow(m_row);
0289     checkModified();
0290   }
0291 }
0292 
0293 void TableFieldWidget::slotRemoveRow() {
0294   if(m_row > -1) {
0295     m_table->removeRow(m_row);
0296     checkModified();
0297   }
0298 }
0299 
0300 void TableFieldWidget::slotMoveRowUp() {
0301   if(m_row < 1 || m_row > m_table->rowCount()-1) {
0302     return;
0303   }
0304   m_table->blockSignals(true);
0305   for(int col = 0; col < m_table->columnCount(); ++col) {
0306     QTableWidgetItem* item1 = m_table->takeItem(m_row-1, col);
0307     QTableWidgetItem* item2 = m_table->takeItem(m_row  , col);
0308     if(item1) {
0309       m_table->setItem(m_row  , col, item1);
0310     }
0311     if(item2) {
0312       m_table->setItem(m_row-1, col, item2);
0313     }
0314   }
0315   m_table->blockSignals(false);
0316   checkModified();
0317 }
0318 
0319 void TableFieldWidget::slotMoveRowDown() {
0320   if(m_row < 0 || m_row > m_table->rowCount()-2) {
0321     return;
0322   }
0323   m_table->blockSignals(true);
0324   for(int col = 0; col < m_table->columnCount(); ++col) {
0325     QTableWidgetItem* item1 = m_table->takeItem(m_row  , col);
0326     QTableWidgetItem* item2 = m_table->takeItem(m_row+1, col);
0327     if(item1) {
0328       m_table->setItem(m_row+1, col, item1);
0329     }
0330     if(item2) {
0331       m_table->setItem(m_row  , col, item2);
0332     }
0333   }
0334   m_table->blockSignals(false);
0335   checkModified();
0336 }
0337 
0338 void TableFieldWidget::slotClear() {
0339   clearImpl();
0340 }