File indexing completed on 2025-02-23 04:34:24

0001 /**
0002  * \file frametable.cpp
0003  * Table to edit frames.
0004  *
0005  * \b Project: Kid3
0006  * \author Urs Fleisch
0007  * \date 05 Sep 2007
0008  *
0009  * Copyright (C) 2007-2024  Urs Fleisch
0010  *
0011  * This file is part of Kid3.
0012  *
0013  * Kid3 is free software; you can redistribute it and/or modify
0014  * it under the terms of the GNU General Public License as published by
0015  * the Free Software Foundation; either version 2 of the License, or
0016  * (at your option) any later version.
0017  *
0018  * Kid3 is distributed in the hope that it will be useful,
0019  * but WITHOUT ANY WARRANTY; without even the implied warranty of
0020  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0021  * GNU General Public License for more details.
0022  *
0023  * You should have received a copy of the GNU General Public License
0024  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
0025  */
0026 
0027 #include "frametable.h"
0028 #include <QAction>
0029 #include <QPoint>
0030 #include <QHeaderView>
0031 #include <QMenu>
0032 #include <QChildEvent>
0033 #include <QLineEdit>
0034 #include "frametablemodel.h"
0035 #include "frameitemdelegate.h"
0036 
0037 /**
0038  * Constructor.
0039  *
0040  * @param model frame table model
0041  * @param genreModel genre model
0042  * @param parent parent widget
0043  */
0044 FrameTable::FrameTable(FrameTableModel* model, GenreModel* genreModel,
0045                        QWidget* parent)
0046   : QTableView(parent), m_currentEditor(nullptr)
0047 {
0048   setObjectName(QLatin1String("FrameTable"));
0049   setModel(model);
0050   setSelectionMode(SingleSelection);
0051   horizontalHeader()->setSectionResizeMode(FrameTableModel::CI_Value, QHeaderView::Stretch);
0052   // Set a small height instead of hiding the header, so that the column
0053   // widths can still be resized by the user.
0054   if (model->headersEmpty()) {
0055     horizontalHeader()->setFixedHeight(2);
0056   }
0057   verticalHeader()->hide();
0058   if (model->isId3v1()) {
0059     bool insertTemporaryRow = model->rowCount() < 1;
0060     if (insertTemporaryRow)
0061       model->insertRow(0);
0062     setMinimumHeight((Frame::FT_LastV1Frame + 1) * (rowHeight(0) + 1));
0063     if (insertTemporaryRow)
0064       model->removeRow(0);
0065   }
0066   // Set width of first column
0067 #if QT_VERSION >= 0x050b00
0068   int width = fontMetrics().horizontalAdvance(tr("WWW Audio Source") + QLatin1String("WW"));
0069 #else
0070   int width = fontMetrics().width(tr("WWW Audio Source") + QLatin1String("WW"));
0071 #endif
0072   QStyleOptionButton option;
0073   option.initFrom(this);
0074   width += style()->subElementRect(
0075     QStyle::SE_ItemViewItemCheckIndicator, &option, this).width();
0076   setColumnWidth(FrameTableModel::CI_Enable, width);
0077 
0078   horizontalHeader()->setSectionResizeMode(FrameTableModel::CI_Value, QHeaderView::Stretch);
0079   setItemDelegate(new FrameItemDelegate(genreModel, this));
0080   setEditTriggers(AllEditTriggers);
0081   viewport()->installEventFilter(this); // keep track of editors
0082   setContextMenuPolicy(Qt::CustomContextMenu);
0083   connect(this, &QWidget::customContextMenuRequested,
0084       this, &FrameTable::customContextMenu);
0085 }
0086 
0087 /**
0088  * Filters events if this object has been installed as an event filter
0089  * for the watched object.
0090  * This method is reimplemented to keep track of the current open editor.
0091  * It has to be installed on the viewport of the table.
0092  * @param watched watched object
0093  * @param event event
0094  * @return true to filter event out.
0095  */
0096 bool FrameTable::eventFilter(QObject* watched, QEvent* event)
0097 {
0098   if (event) {
0099     if (QEvent::Type type = event->type(); type == QEvent::ChildAdded) {
0100       if (QObject* obj = static_cast<QChildEvent*>(event)->child();
0101           obj && obj->isWidgetType()) {
0102         m_currentEditor = static_cast<QWidget*>(obj);
0103       }
0104     } else if (type == QEvent::ChildRemoved) {
0105       if (m_currentEditor == static_cast<QChildEvent*>(event)->child()) {
0106         m_currentEditor = nullptr;
0107       }
0108     } else if (type == QEvent::WindowDeactivate) {
0109       // this is done to avoid losing focus when the window is deactivated
0110       // while editing a cell (i.e. the cell is not closed by pressing Enter)
0111       if ((state() == QAbstractItemView::EditingState) && m_currentEditor) {
0112         commitData(m_currentEditor);
0113         closeEditor(m_currentEditor, QAbstractItemDelegate::EditPreviousItem);
0114       }
0115     }
0116   }
0117   return QTableView::eventFilter(watched, event);
0118 }
0119 
0120 /**
0121  * Commit data from the current editor.
0122  * This is used to avoid losing the changes in open editors e.g. when
0123  * the file is changed using Alt-Up or Alt-Down.
0124  *
0125  * @return true if data was committed.
0126  */
0127 bool FrameTable::acceptEdit()
0128 {
0129   if ((state() == QAbstractItemView::EditingState) && m_currentEditor) {
0130     commitData(m_currentEditor);
0131     //  close editor to avoid being stuck in QAbstractItemView::NoState
0132     closeEditor(m_currentEditor, QAbstractItemDelegate::NoHint);
0133     return true;
0134   }
0135   return false;
0136 }
0137 
0138 /**
0139  * Get current editor widget if the table is currently in edit state.
0140  * @return current editor widget, 0 if not in edit state.
0141  */
0142 const QWidget* FrameTable::getCurrentEditor() const
0143 {
0144   return state() == EditingState ? m_currentEditor : nullptr;
0145 }
0146 
0147 /**
0148  * Display context menu.
0149  *
0150  * @param row row at which context menu is displayed
0151  * @param col column at which context menu is displayed
0152  * @param pos position where context menu is drawn on screen
0153  */
0154 void FrameTable::contextMenu(int row, int col, const QPoint& pos)
0155 {
0156   if (const auto ftModel =
0157         qobject_cast<const FrameTableModel*>(model());
0158       ftModel && col == 0 && row >= 0) {
0159     QMenu menu(this);
0160     QAction* action = menu.addAction(tr("&Select all"));
0161     connect(action, &QAction::triggered, ftModel, &FrameTableModel::selectAllFrames);
0162     action = menu.addAction(tr("&Deselect all"));
0163     connect(action, &QAction::triggered, ftModel, &FrameTableModel::deselectAllFrames);
0164     menu.setMouseTracking(true);
0165     menu.exec(pos);
0166   }
0167 }
0168 
0169 /**
0170  * Display custom context menu.
0171  *
0172  * @param pos position where context menu is drawn on screen
0173  */
0174 void FrameTable::customContextMenu(const QPoint& pos)
0175 {
0176   if (QModelIndex index = indexAt(pos); index.isValid()) {
0177     contextMenu(index.row(), index.column(), mapToGlobal(pos));
0178   }
0179 }
0180 
0181 /**
0182  * Select in the editor of a value row.
0183  * @param row row number
0184  * @param start start position
0185  * @param length number of characters to select
0186  */
0187 void FrameTable::setValueSelection(int row, int start, int length)
0188 {
0189   if (const auto ftModel =
0190       qobject_cast<const FrameTableModel*>(model())) {
0191     if (QModelIndex idx = ftModel->index(row, FrameTableModel::CI_Value);
0192         idx.isValid()) {
0193       scrollTo(idx);
0194       setCurrentIndex(idx);
0195       edit(idx);
0196       if (auto le = qobject_cast<QLineEdit*>(indexWidget(idx))) {
0197         le->setSelection(start, length);
0198       }
0199     }
0200   }
0201 }