blob: 58bfff50444c696ee275901266bdcac9b773730e [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
** All rights reserved.
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** This file is part of the Qt Linguist of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** GNU Lesser General Public License Usage
** This file may be used under the terms of the GNU Lesser General Public
** License version 2.1 as published by the Free Software Foundation and
** appearing in the file LICENSE.LGPL included in the packaging of this
** file. Please review the following information to ensure the GNU Lesser
** General Public License version 2.1 requirements will be met:
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Nokia gives you certain additional
** rights. These rights are described in the Nokia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU General
** Public License version 3.0 as published by the Free Software Foundation
** and appearing in the file LICENSE.GPL included in the packaging of this
** file. Please review the following information to ensure the GNU General
** Public License version 3.0 requirements will be met:
** http://www.gnu.org/copyleft/gpl.html.
**
** Other Usage
** Alternatively, this file may be used in accordance with the terms and
** conditions contained in a signed written agreement between you and Nokia.
**
**
**
**
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "messageeditorwidgets.h"
#include "messagehighlighter.h"
#include <translator.h>
#include <QAbstractTextDocumentLayout>
#include <QAction>
#include <QApplication>
#include <QClipboard>
#include <QDebug>
#include <QLayout>
#include <QMenu>
#include <QMessageBox>
#include <QPainter>
#include <QScrollArea>
#include <QTextBlock>
#include <QTextDocumentFragment>
#include <QToolButton>
#include <QVBoxLayout>
QT_BEGIN_NAMESPACE
ExpandingTextEdit::ExpandingTextEdit(QWidget *parent)
: QTextEdit(parent)
{
setSizePolicy(QSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Expanding));
setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
QAbstractTextDocumentLayout *docLayout = document()->documentLayout();
connect(docLayout, SIGNAL(documentSizeChanged(QSizeF)), SLOT(updateHeight(QSizeF)));
connect(this, SIGNAL(cursorPositionChanged()), this, SLOT(reallyEnsureCursorVisible()));
m_minimumHeight = qRound(docLayout->documentSize().height()) + frameWidth() * 2;
}
void ExpandingTextEdit::updateHeight(const QSizeF &documentSize)
{
m_minimumHeight = qRound(documentSize.height()) + frameWidth() * 2;
updateGeometry();
}
QSize ExpandingTextEdit::sizeHint() const
{
return QSize(100, m_minimumHeight);
}
QSize ExpandingTextEdit::minimumSizeHint() const
{
return QSize(100, m_minimumHeight);
}
void ExpandingTextEdit::reallyEnsureCursorVisible()
{
QObject *ancestor = parent();
while (ancestor) {
QScrollArea *scrollArea = qobject_cast<QScrollArea*>(ancestor);
if (scrollArea &&
(scrollArea->verticalScrollBarPolicy() != Qt::ScrollBarAlwaysOff &&
scrollArea->horizontalScrollBarPolicy() != Qt::ScrollBarAlwaysOff)) {
const QRect &r = cursorRect();
const QPoint &c = mapTo(scrollArea->widget(), r.center());
scrollArea->ensureVisible(c.x(), c.y());
break;
}
ancestor = ancestor->parent();
}
}
FormatTextEdit::FormatTextEdit(QWidget *parent)
: ExpandingTextEdit(parent)
{
setLineWrapMode(QTextEdit::WidgetWidth);
setAcceptRichText(false);
QTextOption option = document()->defaultTextOption();
option.setFlags(option.flags()
| QTextOption::ShowLineAndParagraphSeparators
| QTextOption::ShowTabsAndSpaces);
document()->setDefaultTextOption(option);
// Do not set different background if disabled
QPalette p = palette();
p.setColor(QPalette::Disabled, QPalette::Base, p.color(QPalette::Active, QPalette::Base));
setPalette(p);
setEditable(true);
m_highlighter = new MessageHighlighter(this);
}
void FormatTextEdit::setEditable(bool editable)
{
// save default frame style
static int framed = frameStyle();
static Qt::FocusPolicy defaultFocus = focusPolicy();
if (editable) {
setFrameStyle(framed);
setFocusPolicy(defaultFocus);
} else {
setFrameStyle(QFrame::NoFrame | QFrame::Plain);
setFocusPolicy(Qt::NoFocus);
}
setReadOnly(!editable);
}
void FormatTextEdit::setPlainText(const QString &text, bool userAction)
{
if (!userAction) {
// Prevent contentsChanged signal
bool oldBlockState = blockSignals(true);
document()->setUndoRedoEnabled(false);
ExpandingTextEdit::setPlainText(text);
// highlighter is out of sync because of blocked signals
m_highlighter->rehighlight();
document()->setUndoRedoEnabled(true);
blockSignals(oldBlockState);
} else {
ExpandingTextEdit::setPlainText(text);
}
}
FormWidget::FormWidget(const QString &label, bool isEditable, QWidget *parent)
: QWidget(parent),
m_hideWhenEmpty(false)
{
QVBoxLayout *layout = new QVBoxLayout;
layout->setMargin(0);
m_label = new QLabel(this);
QFont fnt;
fnt.setBold(true);
m_label->setFont(fnt);
m_label->setText(label);
layout->addWidget(m_label);
m_editor = new FormatTextEdit(this);
m_editor->setEditable(isEditable);
//m_textEdit->setWhatsThis(tr("This area shows text from an auxillary translation."));
layout->addWidget(m_editor);
setLayout(layout);
connect(m_editor, SIGNAL(textChanged()), SLOT(slotTextChanged()));
connect(m_editor, SIGNAL(selectionChanged()), SLOT(slotSelectionChanged()));
connect(m_editor, SIGNAL(cursorPositionChanged()), SIGNAL(cursorPositionChanged()));
}
void FormWidget::slotTextChanged()
{
emit textChanged(m_editor);
}
void FormWidget::slotSelectionChanged()
{
emit selectionChanged(m_editor);
}
void FormWidget::setTranslation(const QString &text, bool userAction)
{
m_editor->setPlainText(text, userAction);
if (m_hideWhenEmpty)
setHidden(text.isEmpty());
}
void FormWidget::setEditingEnabled(bool enable)
{
// Use read-only state so that the text can still be copied
m_editor->setReadOnly(!enable);
m_label->setEnabled(enable);
}
class ButtonWrapper : public QWidget
{
// no Q_OBJECT: no need to, and don't want the useless moc file
public:
ButtonWrapper(QWidget *wrapee, QWidget *relator) : m_wrapee(wrapee)
{
setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Ignored);
QBoxLayout *box = new QVBoxLayout;
box->setMargin(0);
setLayout(box);
box->addWidget(wrapee, 0, Qt::AlignBottom);
if (relator)
relator->installEventFilter(this);
}
protected:
virtual bool eventFilter(QObject *object, QEvent *event)
{
if (event->type() == QEvent::Resize) {
QWidget *relator = static_cast<QWidget *>(object);
setFixedHeight((relator->height() + layout()->spacing() + m_wrapee->height()) / 2);
}
return false;
}
private:
QWidget *m_wrapee;
};
FormMultiWidget::FormMultiWidget(const QString &label, QWidget *parent)
: QWidget(parent),
m_hideWhenEmpty(false),
m_multiEnabled(false),
m_plusIcon(QIcon(QLatin1String(":/images/plus.png"))), // make static
m_minusIcon(QIcon(QLatin1String(":/images/minus.png")))
{
m_label = new QLabel(this);
QFont fnt;
fnt.setBold(true);
m_label->setFont(fnt);
m_label->setText(label);
m_plusButtons.append(
new ButtonWrapper(makeButton(m_plusIcon, SLOT(plusButtonClicked())), 0));
}
QAbstractButton *FormMultiWidget::makeButton(const QIcon &icon, const char *slot)
{
QAbstractButton *btn = new QToolButton(this);
btn->setIcon(icon);
btn->setFixedSize(icon.availableSizes().first() /* + something */);
btn->setFocusPolicy(Qt::NoFocus);
connect(btn, SIGNAL(clicked()), slot);
return btn;
}
void FormMultiWidget::addEditor(int idx)
{
FormatTextEdit *editor = new FormatTextEdit(this);
m_editors.insert(idx, editor);
m_minusButtons.insert(idx, makeButton(m_minusIcon, SLOT(minusButtonClicked())));
m_plusButtons.insert(idx + 1,
new ButtonWrapper(makeButton(m_plusIcon, SLOT(plusButtonClicked())), editor));
connect(editor, SIGNAL(textChanged()), SLOT(slotTextChanged()));
connect(editor, SIGNAL(selectionChanged()), SLOT(slotSelectionChanged()));
connect(editor, SIGNAL(cursorPositionChanged()), SIGNAL(cursorPositionChanged()));
editor->installEventFilter(this);
emit editorCreated(editor);
}
bool FormMultiWidget::eventFilter(QObject *watched, QEvent *event)
{
int i = 0;
while (m_editors.at(i) != watched)
if (++i >= m_editors.count()) // Happens when deleting an editor
return false;
if (event->type() == QEvent::FocusOut) {
m_minusButtons.at(i)->setToolTip(QString());
m_plusButtons.at(i)->setToolTip(QString());
m_plusButtons.at(i + 1)->setToolTip(QString());
} else if (event->type() == QEvent::FocusIn) {
m_minusButtons.at(i)->setToolTip(/*: translate, but don't change */ tr("Alt+Delete"));
m_plusButtons.at(i)->setToolTip(/*: translate, but don't change */ tr("Shift+Alt+Insert"));
m_plusButtons.at(i + 1)->setToolTip(/*: translate, but don't change */ tr("Alt+Insert"));
} else if (event->type() == QEvent::KeyPress) {
QKeyEvent *ke = static_cast<QKeyEvent *>(event);
if (ke->modifiers() & Qt::AltModifier) {
if (ke->key() == Qt::Key_Delete) {
deleteEditor(i);
return true;
} else if (ke->key() == Qt::Key_Insert) {
if (!(ke->modifiers() & Qt::ShiftModifier))
++i;
insertEditor(i);
return true;
}
}
}
return false;
}
void FormMultiWidget::updateLayout()
{
delete layout();
QGridLayout *layout = new QGridLayout;
layout->setMargin(0);
setLayout(layout);
bool variants = m_multiEnabled && m_label->isEnabled();
layout->addWidget(m_label, 0, 0, 1, variants ? 3 : 1);
for (int i = 0; i < m_plusButtons.count(); ++i) {
if (variants)
layout->addWidget(m_plusButtons.at(i), 1 + i * 2, 0, 2, 1, Qt::AlignTop);
m_plusButtons.at(i)->setVisible(variants);
}
for (int j = 0; j < m_minusButtons.count(); ++j) {
if (variants)
layout->addWidget(m_minusButtons.at(j), 2 + j * 2, 2, 2, 1, Qt::AlignVCenter);
m_minusButtons.at(j)->setVisible(variants);
}
for (int k = 0; k < m_editors.count(); ++k)
layout->addWidget(m_editors.at(k), 2 + k * 2, variants ? 1 : 0, 2, 1, Qt::AlignVCenter);
updateGeometry();
}
void FormMultiWidget::slotTextChanged()
{
emit textChanged(static_cast<QTextEdit *>(sender()));
}
void FormMultiWidget::slotSelectionChanged()
{
emit selectionChanged(static_cast<QTextEdit *>(sender()));
}
void FormMultiWidget::setTranslation(const QString &text, bool userAction)
{
QStringList texts = text.split(QChar(Translator::BinaryVariantSeparator), QString::KeepEmptyParts);
while (m_editors.count() > texts.count()) {
delete m_minusButtons.takeLast();
delete m_plusButtons.takeLast();
delete m_editors.takeLast();
}
while (m_editors.count() < texts.count())
addEditor(m_editors.count());
updateLayout();
for (int i = 0; i < texts.count(); ++i)
// XXX this will emit n textChanged signals
m_editors.at(i)->setPlainText(texts.at(i), userAction);
if (m_hideWhenEmpty)
setHidden(text.isEmpty());
}
QString FormMultiWidget::getTranslation() const
{
QString ret;
for (int i = 0; i < m_editors.count(); ++i) {
if (i)
ret += QChar(Translator::BinaryVariantSeparator);
ret += m_editors.at(i)->toPlainText();
}
return ret;
}
void FormMultiWidget::setEditingEnabled(bool enable)
{
// Use read-only state so that the text can still be copied
for (int i = 0; i < m_editors.count(); ++i)
m_editors.at(i)->setReadOnly(!enable);
m_label->setEnabled(enable);
if (m_multiEnabled)
updateLayout();
}
void FormMultiWidget::setMultiEnabled(bool enable)
{
m_multiEnabled = enable;
if (m_label->isEnabled())
updateLayout();
}
void FormMultiWidget::minusButtonClicked()
{
int i = 0;
while (m_minusButtons.at(i) != sender())
++i;
deleteEditor(i);
}
void FormMultiWidget::plusButtonClicked()
{
QWidget *btn = static_cast<QAbstractButton *>(sender())->parentWidget();
int i = 0;
while (m_plusButtons.at(i) != btn)
++i;
insertEditor(i);
}
void FormMultiWidget::deleteEditor(int idx)
{
if (m_editors.count() == 1) {
// Don't just clear(), so the undo history is not lost
QTextCursor c = m_editors.first()->textCursor();
c.select(QTextCursor::Document);
c.removeSelectedText();
} else {
if (!m_editors.at(idx)->toPlainText().isEmpty()) {
if (QMessageBox::question(topLevelWidget(), tr("Confirmation - Qt Linguist"),
tr("Delete non-empty length variant?"),
QMessageBox::Yes|QMessageBox::No, QMessageBox::Yes)
!= QMessageBox::Yes)
return;
}
delete m_editors.takeAt(idx);
delete m_minusButtons.takeAt(idx);
delete m_plusButtons.takeAt(idx + 1);
updateLayout();
emit textChanged(m_editors.at((m_editors.count() == idx) ? idx - 1 : idx));
}
}
void FormMultiWidget::insertEditor(int idx)
{
addEditor(idx);
updateLayout();
emit textChanged(m_editors.at(idx));
}
QT_END_NAMESPACE