2 ******************************************************************************
4 * @file submiteditorwidget.cpp
5 * @author The OpenPilot Team, http://www.openpilot.org Copyright (C) 2010.
6 * Parts by Nokia Corporation (qt-info@nokia.com) Copyright (C) 2009.
8 * @see The GNU Public License (GPL) Version 3
12 *****************************************************************************/
14 * This program is free software; you can redistribute it and/or modify
15 * it under the terms of the GNU General Public License as published by
16 * the Free Software Foundation; either version 3 of the License, or
17 * (at your option) any later version.
19 * This program is distributed in the hope that it will be useful, but
20 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
21 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
24 * You should have received a copy of the GNU General Public License along
25 * with this program; if not, write to the Free Software Foundation, Inc.,
26 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
29 #include "submiteditorwidget.h"
30 #include "submitfieldwidget.h"
31 #include "ui_submiteditorwidget.h"
33 #include <QtCore/QDebug>
34 #include <QtCore/QPointer>
35 #include <QtCore/QTimer>
37 #include <QPushButton>
39 #include <QHBoxLayout>
40 #include <QToolButton>
41 #include <QSpacerItem>
44 enum { defaultLineWidth
= 72 };
47 // QActionPushButton: A push button tied to an action
48 // (similar to a QToolButton)
49 class QActionPushButton
: public QPushButton
{
52 explicit QActionPushButton(QAction
*a
);
58 QActionPushButton::QActionPushButton(QAction
*a
) :
59 QPushButton(a
->icon(), a
->text())
61 connect(a
, SIGNAL(changed()), this, SLOT(actionChanged()));
62 connect(this, SIGNAL(clicked()), a
, SLOT(trigger()));
63 setEnabled(a
->isEnabled());
66 void QActionPushButton::actionChanged()
68 if (const QAction
* a
= qobject_cast
<QAction
*>(sender())) {
69 setEnabled(a
->isEnabled());
73 // Helpers to retrieve model data
74 static inline bool listModelChecked(const QAbstractItemModel
*model
, int row
, int column
= 0)
76 const QModelIndex checkableIndex
= model
->index(row
, column
, QModelIndex());
78 return model
->data(checkableIndex
, Qt::CheckStateRole
).toInt() == Qt::Checked
;
81 static inline QString
listModelText(const QAbstractItemModel
*model
, int row
, int column
)
83 const QModelIndex index
= model
->index(row
, column
, QModelIndex());
85 return model
->data(index
, Qt::DisplayRole
).toString();
88 // Find a check item in a model
89 static bool listModelContainsCheckedItem(const QAbstractItemModel
*model
)
91 const int count
= model
->rowCount();
93 for (int i
= 0; i
< count
; i
++) {
94 if (listModelChecked(model
, i
, 0)) {
101 // Convenience to extract a list of selected indexes
102 QList
<int> selectedRows(const QAbstractItemView
*view
)
104 const QModelIndexList indexList
= view
->selectionModel()->selectedRows(0);
106 if (indexList
.empty()) {
110 const QModelIndexList::const_iterator cend
= indexList
.constEnd();
111 for (QModelIndexList::const_iterator it
= indexList
.constBegin(); it
!= cend
; ++it
) {
112 rc
.push_back(it
->row());
117 // ----------- SubmitEditorWidgetPrivate
119 struct SubmitEditorWidgetPrivate
{
120 // A pair of position/action to extend context menus
121 typedef QPair
<int, QPointer
<QAction
> > AdditionalContextMenuAction
;
123 SubmitEditorWidgetPrivate();
125 Ui::SubmitEditorWidget m_ui
;
126 bool m_filesSelected
;
128 int m_fileNameColumn
;
131 QList
<AdditionalContextMenuAction
> descriptionEditContextMenuActions
;
132 QVBoxLayout
*m_fieldLayout
;
133 QList
<SubmitFieldWidget
*> m_fieldWidgets
;
137 SubmitEditorWidgetPrivate::SubmitEditorWidgetPrivate() :
138 m_filesSelected(false),
139 m_filesChecked(false),
143 m_lineWidth(defaultLineWidth
)
146 SubmitEditorWidget::SubmitEditorWidget(QWidget
*parent
) :
148 m_d(new SubmitEditorWidgetPrivate
)
150 m_d
->m_ui
.setupUi(this);
151 m_d
->m_ui
.description
->setContextMenuPolicy(Qt::CustomContextMenu
);
152 m_d
->m_ui
.description
->setLineWrapMode(QTextEdit::NoWrap
);
153 m_d
->m_ui
.description
->setWordWrapMode(QTextOption::WordWrap
);
154 connect(m_d
->m_ui
.description
, SIGNAL(customContextMenuRequested(QPoint
)),
155 this, SLOT(editorCustomContextMenuRequested(QPoint
)));
158 m_d
->m_ui
.fileView
->setSelectionMode(QAbstractItemView::ExtendedSelection
);
159 m_d
->m_ui
.fileView
->setRootIsDecorated(false);
160 connect(m_d
->m_ui
.fileView
, SIGNAL(doubleClicked(QModelIndex
)),
161 this, SLOT(diffActivated(QModelIndex
)));
163 setFocusPolicy(Qt::StrongFocus
);
164 setFocusProxy(m_d
->m_ui
.description
);
167 SubmitEditorWidget::~SubmitEditorWidget()
172 void SubmitEditorWidget::registerActions(QAction
*editorUndoAction
, QAction
*editorRedoAction
,
173 QAction
*submitAction
, QAction
*diffAction
)
175 if (editorUndoAction
) {
176 editorUndoAction
->setEnabled(m_d
->m_ui
.description
->document()->isUndoAvailable());
177 connect(m_d
->m_ui
.description
, SIGNAL(undoAvailable(bool)), editorUndoAction
, SLOT(setEnabled(bool)));
178 connect(editorUndoAction
, SIGNAL(triggered()), m_d
->m_ui
.description
, SLOT(undo()));
180 if (editorRedoAction
) {
181 editorRedoAction
->setEnabled(m_d
->m_ui
.description
->document()->isRedoAvailable());
182 connect(m_d
->m_ui
.description
, SIGNAL(redoAvailable(bool)), editorRedoAction
, SLOT(setEnabled(bool)));
183 connect(editorRedoAction
, SIGNAL(triggered()), m_d
->m_ui
.description
, SLOT(redo()));
189 if (const QAbstractItemModel
* model
= m_d
->m_ui
.fileView
->model()) {
190 count
= model
->rowCount();
192 qDebug() << Q_FUNC_INFO
<< submitAction
<< count
<< "items" << m_d
->m_filesChecked
;
194 submitAction
->setEnabled(m_d
->m_filesChecked
);
195 connect(this, SIGNAL(fileCheckStateChanged(bool)), submitAction
, SLOT(setEnabled(bool)));
196 m_d
->m_ui
.buttonLayout
->addWidget(new QActionPushButton(submitAction
));
200 qDebug() << diffAction
<< m_d
->m_filesSelected
;
202 diffAction
->setEnabled(m_d
->m_filesSelected
);
203 connect(this, SIGNAL(fileSelectionChanged(bool)), diffAction
, SLOT(setEnabled(bool)));
204 connect(diffAction
, SIGNAL(triggered()), this, SLOT(triggerDiffSelected()));
205 m_d
->m_ui
.buttonLayout
->addWidget(new QActionPushButton(diffAction
));
209 void SubmitEditorWidget::unregisterActions(QAction
*editorUndoAction
, QAction
*editorRedoAction
,
210 QAction
*submitAction
, QAction
*diffAction
)
212 if (editorUndoAction
) {
213 disconnect(m_d
->m_ui
.description
, SIGNAL(undoAvailableChanged(bool)), editorUndoAction
, SLOT(setEnabled(bool)));
214 disconnect(editorUndoAction
, SIGNAL(triggered()), m_d
->m_ui
.description
, SLOT(undo()));
216 if (editorRedoAction
) {
217 disconnect(m_d
->m_ui
.description
, SIGNAL(redoAvailableChanged(bool)), editorRedoAction
, SLOT(setEnabled(bool)));
218 disconnect(editorRedoAction
, SIGNAL(triggered()), m_d
->m_ui
.description
, SLOT(redo()));
222 disconnect(this, SIGNAL(fileCheckStateChanged(bool)), submitAction
, SLOT(setEnabled(bool)));
226 disconnect(this, SIGNAL(fileSelectionChanged(bool)), diffAction
, SLOT(setEnabled(bool)));
227 disconnect(diffAction
, SIGNAL(triggered()), this, SLOT(triggerDiffSelected()));
231 // Make sure we have one terminating NL
232 static inline QString
trimMessageText(const QString
&t
)
234 QString rc
= t
.trimmed();
236 rc
+= QLatin1Char('\n');
240 // Extract the wrapped text from a text edit, which performs
241 // the wrapping only optically.
242 static QString
wrappedText(const QTextEdit
*e
)
244 const QChar newLine
= QLatin1Char('\n');
246 QTextCursor
cursor(e
->document());
248 cursor
.movePosition(QTextCursor::Start
);
249 while (!cursor
.atEnd()) {
250 cursor
.select(QTextCursor::LineUnderCursor
);
251 rc
+= cursor
.selectedText();
253 cursor
.movePosition(QTextCursor::EndOfLine
); // Mac needs it
254 cursor
.movePosition(QTextCursor::Right
);
259 QString
SubmitEditorWidget::descriptionText() const
261 QString rc
= trimMessageText(lineWrap() ? wrappedText(m_d
->m_ui
.description
) : m_d
->m_ui
.description
->toPlainText());
263 // append field entries
264 foreach(const SubmitFieldWidget
* fw
, m_d
->m_fieldWidgets
)
265 rc
+= fw
->fieldValues();
269 void SubmitEditorWidget::setDescriptionText(const QString
&text
)
271 m_d
->m_ui
.description
->setPlainText(text
);
274 bool SubmitEditorWidget::lineWrap() const
276 return m_d
->m_ui
.description
->lineWrapMode() != QTextEdit::NoWrap
;
279 void SubmitEditorWidget::setLineWrap(bool v
)
282 qDebug() << Q_FUNC_INFO
<< v
;
285 m_d
->m_ui
.description
->setLineWrapColumnOrWidth(m_d
->m_lineWidth
);
286 m_d
->m_ui
.description
->setLineWrapMode(QTextEdit::FixedColumnWidth
);
288 m_d
->m_ui
.description
->setLineWrapMode(QTextEdit::NoWrap
);
292 int SubmitEditorWidget::lineWrapWidth() const
294 return m_d
->m_lineWidth
;
297 void SubmitEditorWidget::setLineWrapWidth(int v
)
300 qDebug() << Q_FUNC_INFO
<< v
<< lineWrap();
302 if (m_d
->m_lineWidth
== v
) {
305 m_d
->m_lineWidth
= v
;
307 m_d
->m_ui
.description
->setLineWrapColumnOrWidth(v
);
311 int SubmitEditorWidget::fileNameColumn() const
313 return m_d
->m_fileNameColumn
;
316 void SubmitEditorWidget::setFileNameColumn(int c
)
318 m_d
->m_fileNameColumn
= c
;
321 QAbstractItemView::SelectionMode
SubmitEditorWidget::fileListSelectionMode() const
323 return m_d
->m_ui
.fileView
->selectionMode();
326 void SubmitEditorWidget::setFileListSelectionMode(QAbstractItemView::SelectionMode sm
)
328 m_d
->m_ui
.fileView
->setSelectionMode(sm
);
331 void SubmitEditorWidget::setFileModel(QAbstractItemModel
*model
)
333 m_d
->m_ui
.fileView
->clearSelection(); // trigger the change signals
335 m_d
->m_ui
.fileView
->setModel(model
);
337 if (model
->rowCount()) {
338 const int columnCount
= model
->columnCount();
339 for (int c
= 0; c
< columnCount
; c
++) {
340 m_d
->m_ui
.fileView
->resizeColumnToContents(c
);
344 connect(model
, SIGNAL(dataChanged(QModelIndex
, QModelIndex
)),
345 this, SLOT(updateSubmitAction()));
346 connect(model
, SIGNAL(modelReset()),
347 this, SLOT(updateSubmitAction()));
348 connect(model
, SIGNAL(rowsInserted(QModelIndex
, int, int)),
349 this, SLOT(updateSubmitAction()));
350 connect(model
, SIGNAL(rowsRemoved(QModelIndex
, int, int)),
351 this, SLOT(updateSubmitAction()));
352 connect(m_d
->m_ui
.fileView
->selectionModel(), SIGNAL(selectionChanged(QItemSelection
, QItemSelection
)),
353 this, SLOT(updateDiffAction()));
357 QAbstractItemModel
*SubmitEditorWidget::fileModel() const
359 return m_d
->m_ui
.fileView
->model();
362 QStringList
SubmitEditorWidget::selectedFiles() const
364 const QList
<int> selection
= selectedRows(m_d
->m_ui
.fileView
);
366 if (selection
.empty()) {
367 return QStringList();
371 const QAbstractItemModel
*model
= m_d
->m_ui
.fileView
->model();
372 const int count
= selection
.size();
373 for (int i
= 0; i
< count
; i
++) {
374 rc
.push_back(listModelText(model
, selection
.at(i
), fileNameColumn()));
379 QStringList
SubmitEditorWidget::checkedFiles() const
382 const QAbstractItemModel
*model
= m_d
->m_ui
.fileView
->model();
387 const int count
= model
->rowCount();
388 for (int i
= 0; i
< count
; i
++) {
389 if (listModelChecked(model
, i
, 0)) {
390 rc
.push_back(listModelText(model
, i
, fileNameColumn()));
396 QTextEdit
*SubmitEditorWidget::descriptionEdit() const
398 return m_d
->m_ui
.description
;
401 void SubmitEditorWidget::triggerDiffSelected()
403 const QStringList sel
= selectedFiles();
406 emit
diffSelected(sel
);
410 void SubmitEditorWidget::diffActivatedDelayed()
412 const QStringList files
= QStringList(listModelText(m_d
->m_ui
.fileView
->model(), m_d
->m_activatedRow
, fileNameColumn()));
413 emit
diffSelected(files
);
416 void SubmitEditorWidget::diffActivated(const QModelIndex
&index
)
418 // We need to delay the signal, otherwise, the diff editor will not
419 // be in the foreground.
420 m_d
->m_activatedRow
= index
.row();
421 QTimer::singleShot(0, this, SLOT(diffActivatedDelayed()));
424 void SubmitEditorWidget::updateActions()
426 updateSubmitAction();
430 // Enable submit depending on having checked files
431 void SubmitEditorWidget::updateSubmitAction()
433 const bool newFilesCheckedState
= hasCheckedFiles();
435 if (m_d
->m_filesChecked
!= newFilesCheckedState
) {
436 m_d
->m_filesChecked
= newFilesCheckedState
;
437 emit
fileCheckStateChanged(m_d
->m_filesChecked
);
441 // Enable diff depending on selected files
442 void SubmitEditorWidget::updateDiffAction()
444 const bool filesSelected
= hasSelection();
446 if (m_d
->m_filesSelected
!= filesSelected
) {
447 m_d
->m_filesSelected
= filesSelected
;
448 emit
fileSelectionChanged(m_d
->m_filesSelected
);
452 bool SubmitEditorWidget::hasSelection() const
454 // Not present until model is set
455 if (const QItemSelectionModel
* sm
= m_d
->m_ui
.fileView
->selectionModel()) {
456 return sm
->hasSelection();
461 bool SubmitEditorWidget::hasCheckedFiles() const
463 if (const QAbstractItemModel
* model
= m_d
->m_ui
.fileView
->model()) {
464 return listModelContainsCheckedItem(model
);
469 void SubmitEditorWidget::changeEvent(QEvent
*e
)
471 QWidget::changeEvent(e
);
474 case QEvent::LanguageChange
:
475 m_d
->m_ui
.retranslateUi(this);
482 void SubmitEditorWidget::insertTopWidget(QWidget
*w
)
484 m_d
->m_ui
.vboxLayout
->insertWidget(0, w
);
487 void SubmitEditorWidget::addSubmitFieldWidget(SubmitFieldWidget
*f
)
489 if (!m_d
->m_fieldLayout
) {
490 // VBox with horizontal, expanding spacer
491 m_d
->m_fieldLayout
= new QVBoxLayout
;
492 QHBoxLayout
*outerLayout
= new QHBoxLayout
;
493 outerLayout
->addLayout(m_d
->m_fieldLayout
);
494 outerLayout
->addItem(new QSpacerItem(0, 0, QSizePolicy::Expanding
, QSizePolicy::Ignored
));
495 QBoxLayout
*descrLayout
= qobject_cast
<QBoxLayout
*>(m_d
->m_ui
.descriptionBox
->layout());
496 Q_ASSERT(descrLayout
);
497 descrLayout
->addLayout(outerLayout
);
499 m_d
->m_fieldLayout
->addWidget(f
);
500 m_d
->m_fieldWidgets
.push_back(f
);
503 QList
<SubmitFieldWidget
*> SubmitEditorWidget::submitFieldWidgets() const
505 return m_d
->m_fieldWidgets
;
508 void SubmitEditorWidget::addDescriptionEditContextMenuAction(QAction
*a
)
510 m_d
->descriptionEditContextMenuActions
.push_back(SubmitEditorWidgetPrivate::AdditionalContextMenuAction(-1, a
));
513 void SubmitEditorWidget::insertDescriptionEditContextMenuAction(int pos
, QAction
*a
)
515 m_d
->descriptionEditContextMenuActions
.push_back(SubmitEditorWidgetPrivate::AdditionalContextMenuAction(pos
, a
));
518 void SubmitEditorWidget::editorCustomContextMenuRequested(const QPoint
&pos
)
520 QMenu
*menu
= m_d
->m_ui
.description
->createStandardContextMenu();
523 foreach(const SubmitEditorWidgetPrivate::AdditionalContextMenuAction
& a
, m_d
->descriptionEditContextMenuActions
) {
526 menu
->insertAction(menu
->actions().at(a
.first
), a
.second
);
528 menu
->addAction(a
.second
);
532 menu
->exec(m_d
->m_ui
.description
->mapToGlobal(pos
));
537 #include "submiteditorwidget.moc"