mirror of
https://github.com/JakubMelka/PDF4QT.git
synced 2025-06-05 21:59:17 +02:00
Issue #118: Refactoring of forms
This commit is contained in:
@@ -57,7 +57,6 @@ add_library(Pdf4QtLibCore SHARED
|
||||
sources/pdfsignaturehandler.cpp
|
||||
sources/pdfsnapper.cpp
|
||||
sources/pdfstructuretree.cpp
|
||||
sources/pdftexteditpseudowidget.cpp
|
||||
sources/pdftextlayout.cpp
|
||||
sources/pdftransparencyrenderer.cpp
|
||||
sources/pdfutils.cpp
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -485,56 +485,10 @@ private:
|
||||
PDFWidgetToFormFieldMapping m_widgetToFormField;
|
||||
};
|
||||
|
||||
/// Base class for editors of form fields. It enables editation
|
||||
/// of form fields, such as entering text, clicking on check box etc.
|
||||
class PDFFormFieldWidgetEditor
|
||||
{
|
||||
public:
|
||||
explicit PDFFormFieldWidgetEditor(PDFFormManager* formManager, PDFFormWidget formWidget);
|
||||
virtual ~PDFFormFieldWidgetEditor() = default;
|
||||
|
||||
virtual void shortcutOverrideEvent(QWidget* widget, QKeyEvent* event);
|
||||
virtual void keyPressEvent(QWidget* widget, QKeyEvent* event);
|
||||
virtual void keyReleaseEvent(QWidget* widget, QKeyEvent* event);
|
||||
virtual void mousePressEvent(QWidget* widget, QMouseEvent* event, const QPointF& mousePagePosition);
|
||||
virtual void mouseDoubleClickEvent(QWidget* widget, QMouseEvent* event, const QPointF& mousePagePosition);
|
||||
virtual void mouseReleaseEvent(QWidget* widget, QMouseEvent* event, const QPointF& mousePagePosition);
|
||||
virtual void mouseMoveEvent(QWidget* widget, QMouseEvent* event, const QPointF& mousePagePosition);
|
||||
virtual void wheelEvent(QWidget* widget, QWheelEvent* event, const QPointF& mousePagePosition);
|
||||
virtual void reloadValue() { }
|
||||
virtual bool isEditorDrawEnabled() const { return false; }
|
||||
virtual QRectF getActiveEditorRectangle() const { return QRectF(); }
|
||||
|
||||
const PDFFormWidget* getFormWidget() const { return &m_formWidget; }
|
||||
PDFFormField* getFormField() const { return m_formWidget.getParent(); }
|
||||
PDFObjectReference getWidgetAnnotation() const { return m_formWidget.getWidget(); }
|
||||
|
||||
void setFocus(bool hasFocus);
|
||||
|
||||
/// Draw form field widget using given parameters. It is used, when
|
||||
/// we want to draw editor contents on the painter using parameters.
|
||||
/// Parameter \p edit decides, if editor is drawn, or static contents
|
||||
/// based on field value is drawn.
|
||||
/// \param parameters Parameters
|
||||
/// \param edit Draw editor or static contents
|
||||
virtual void draw(AnnotationDrawParameters& parameters, bool edit) const;
|
||||
|
||||
protected:
|
||||
/// This function is called every time, the focus state changes
|
||||
/// \param focused If editor was focused, or not
|
||||
virtual void setFocusImpl(bool focused) { Q_UNUSED(focused); }
|
||||
|
||||
void performKeypadNavigation(QWidget* widget, QKeyEvent* event);
|
||||
|
||||
PDFFormManager* m_formManager;
|
||||
PDFFormWidget m_formWidget;
|
||||
bool m_hasFocus;
|
||||
};
|
||||
|
||||
/// Form manager. Manages all form widgets functionality - triggers actions,
|
||||
/// edits fields, updates annotation appearances, etc. Valid pointer to annotation
|
||||
/// manager is requirement.
|
||||
class PDF4QTLIBCORESHARED_EXPORT PDFFormManager : public QObject, public IDrawWidgetInputInterface
|
||||
class PDF4QTLIBCORESHARED_EXPORT PDFFormManager : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
@@ -542,7 +496,7 @@ private:
|
||||
using BaseClass = QObject;
|
||||
|
||||
public:
|
||||
explicit PDFFormManager(PDFDrawWidgetProxy* proxy, QObject* parent);
|
||||
explicit PDFFormManager(QObject* parent);
|
||||
virtual ~PDFFormManager() override;
|
||||
|
||||
enum FormAppearanceFlag
|
||||
@@ -599,20 +553,6 @@ public:
|
||||
/// \param functor Functor to apply
|
||||
void modify(const std::function<void(PDFFormField*)>& functor) const;
|
||||
|
||||
/// Sets focus to the editor. Is is allowed to pass nullptr to this
|
||||
/// function, it means that no editor is focused.
|
||||
/// \param editor Editor to be focused
|
||||
void setFocusToEditor(PDFFormFieldWidgetEditor* editor);
|
||||
|
||||
/// Tries to set focus on next/previous form field in the focus chain.
|
||||
/// Function returns true, if focus has been successfully set.
|
||||
/// \param next Focus next (true) or previous (false) widget
|
||||
bool focusNextPrevFormField(bool next);
|
||||
|
||||
/// Returns true, if widget is focused.
|
||||
/// \param widget Widget annotation reference
|
||||
bool isFocused(PDFObjectReference widget) const;
|
||||
|
||||
/// Tries to find appropriate action and returns it. If action is not found,
|
||||
/// then nullptr is returned.
|
||||
/// \param actionType Action to be performed
|
||||
@@ -622,36 +562,12 @@ public:
|
||||
/// Returns default form apperance flags
|
||||
static constexpr FormAppearanceFlags getDefaultApperanceFlags() { return FormAppearanceFlags(HighlightFields | HighlightRequiredFields); }
|
||||
|
||||
struct MouseEventInfo
|
||||
{
|
||||
/// Form field under mouse event, nullptr, if
|
||||
/// no form field is under mouse button.
|
||||
PDFFormField* formField = nullptr;
|
||||
|
||||
/// Form field widget editor, which is associated
|
||||
/// with given form field.
|
||||
PDFFormFieldWidgetEditor* editor = nullptr;
|
||||
|
||||
/// Mouse position in form field coordinate space
|
||||
QPointF mousePosition;
|
||||
|
||||
/// Matrix, which maps from device space to widget space
|
||||
QTransform deviceToWidget;
|
||||
|
||||
/// Returns true, if mouse event info is valid, i.e.
|
||||
/// mouse event occurs above some form field.
|
||||
bool isValid() const { return editor != nullptr; }
|
||||
};
|
||||
|
||||
/// Tries to set value to the form field
|
||||
void setFormFieldValue(PDFFormField::SetValueParameters parameters);
|
||||
|
||||
/// Get widget rectangle (from annotation)
|
||||
QRectF getWidgetRectangle(const PDFFormWidget& widget) const;
|
||||
|
||||
/// Returns editor for form field
|
||||
PDFFormFieldWidgetEditor* getEditor(const PDFFormField* formField) const;
|
||||
|
||||
/// Is committing data disabled?
|
||||
bool isCommitDisabled() const { return m_isCommitDisabled; }
|
||||
|
||||
@@ -660,23 +576,6 @@ public:
|
||||
/// \param action Reset action
|
||||
void performResetAction(const PDFActionResetForm* action);
|
||||
|
||||
// interface IDrawWidgetInputInterface
|
||||
|
||||
virtual void shortcutOverrideEvent(QWidget* widget, QKeyEvent* event) override;
|
||||
virtual void keyPressEvent(QWidget* widget, QKeyEvent* event) override;
|
||||
virtual void keyReleaseEvent(QWidget* widget, QKeyEvent* event) override;
|
||||
virtual void mousePressEvent(QWidget* widget, QMouseEvent* event) override;
|
||||
virtual void mouseDoubleClickEvent(QWidget* widget, QMouseEvent* event) override;
|
||||
virtual void mouseReleaseEvent(QWidget* widget, QMouseEvent* event) override;
|
||||
virtual void mouseMoveEvent(QWidget* widget, QMouseEvent* event) override;
|
||||
virtual void wheelEvent(QWidget* widget, QWheelEvent* event) override;
|
||||
|
||||
/// Returns tooltip generated from annotation
|
||||
virtual QString getTooltip() const override { return QString(); }
|
||||
|
||||
/// Returns current cursor
|
||||
virtual const std::optional<QCursor>& getCursor() const override;
|
||||
|
||||
/// Draws XFA form, or does nothing, if XFA form is not present
|
||||
/// \param pagePointToDevicePointMatrix Page point to device point matrix
|
||||
/// \param page Page
|
||||
@@ -692,44 +591,14 @@ public:
|
||||
/// is emitted.
|
||||
void performPaging();
|
||||
|
||||
virtual int getInputPriority() const override { return FormPriority; }
|
||||
protected:
|
||||
virtual void updateFieldValues();
|
||||
|
||||
signals:
|
||||
void actionTriggered(const pdf::PDFAction* action);
|
||||
void documentModified(pdf::PDFModifiedDocument document);
|
||||
|
||||
private:
|
||||
void updateFormWidgetEditors();
|
||||
void updateFieldValues();
|
||||
|
||||
MouseEventInfo getMouseEventInfo(QWidget* widget, QPoint point);
|
||||
|
||||
struct MouseGrabInfo
|
||||
{
|
||||
MouseEventInfo info;
|
||||
int mouseGrabNesting = 0;
|
||||
|
||||
bool isMouseGrabbed() const { return mouseGrabNesting > 0; }
|
||||
};
|
||||
|
||||
bool isMouseGrabbed() const { return m_mouseGrabInfo.isMouseGrabbed(); }
|
||||
|
||||
/// Grabs mouse input, if mouse is already grabbed, or if event
|
||||
/// is accepted. When mouse is grabbed, then mouse input events
|
||||
/// are sent to active editor and are automatically accepted.
|
||||
/// \param info Mouse event info
|
||||
/// \param event Mouse event
|
||||
void grabMouse(const MouseEventInfo& info, QMouseEvent* event);
|
||||
|
||||
/// Release mouse input
|
||||
/// \param info Mouse event info
|
||||
/// \param event Mouse event
|
||||
void ungrabMouse(const MouseEventInfo& info, QMouseEvent* event);
|
||||
|
||||
/// Releases all form widget editors
|
||||
void clearEditors();
|
||||
|
||||
PDFDrawWidgetProxy* m_proxy;
|
||||
PDFAnnotationManager* m_annotationManager;
|
||||
const PDFDocument* m_document;
|
||||
FormAppearanceFlags m_flags;
|
||||
@@ -737,11 +606,6 @@ private:
|
||||
bool m_isCommitDisabled;
|
||||
|
||||
PDFXFAEngine m_xfaEngine;
|
||||
|
||||
std::vector<PDFFormFieldWidgetEditor*> m_widgetEditors;
|
||||
PDFFormFieldWidgetEditor* m_focusedEditor;
|
||||
MouseGrabInfo m_mouseGrabInfo;
|
||||
std::optional<QCursor> m_mouseCursor;
|
||||
};
|
||||
|
||||
} // namespace pdf
|
||||
|
@@ -1,938 +0,0 @@
|
||||
// Copyright (C) 2022 Jakub Melka
|
||||
//
|
||||
// This file is part of PDF4QT.
|
||||
//
|
||||
// PDF4QT is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// with the written consent of the copyright owner, any later version.
|
||||
//
|
||||
// PDF4QT is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with PDF4QT. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
#include "pdftexteditpseudowidget.h"
|
||||
#include "pdfpainterutils.h"
|
||||
|
||||
#include <QStyle>
|
||||
#include <QKeyEvent>
|
||||
#include <QMouseEvent>
|
||||
#include <QCoreApplication>
|
||||
#include <QClipboard>
|
||||
|
||||
namespace pdf
|
||||
{
|
||||
|
||||
PDFTextEditPseudowidget::PDFTextEditPseudowidget(PDFFormField::FieldFlags flags) :
|
||||
m_flags(flags),
|
||||
m_selectionStart(0),
|
||||
m_selectionEnd(0),
|
||||
m_positionCursor(0),
|
||||
m_maxTextLength(0)
|
||||
{
|
||||
m_textLayout.setCacheEnabled(true);
|
||||
m_passwordReplacementCharacter = QChar(QCoreApplication::style()->styleHint(QStyle::SH_LineEdit_PasswordCharacter));
|
||||
}
|
||||
|
||||
void PDFTextEditPseudowidget::shortcutOverrideEvent(QWidget* widget, QKeyEvent* event)
|
||||
{
|
||||
Q_UNUSED(widget);
|
||||
constexpr QKeySequence::StandardKey acceptedKeys[] = { QKeySequence::Delete, QKeySequence::Cut, QKeySequence::Copy, QKeySequence::Paste,
|
||||
QKeySequence::SelectAll, QKeySequence::MoveToNextChar, QKeySequence::MoveToPreviousChar,
|
||||
QKeySequence::MoveToNextWord, QKeySequence::MoveToPreviousWord, QKeySequence::MoveToNextLine,
|
||||
QKeySequence::MoveToPreviousLine, QKeySequence::MoveToStartOfLine, QKeySequence::MoveToEndOfLine,
|
||||
QKeySequence::MoveToStartOfBlock, QKeySequence::MoveToEndOfBlock, QKeySequence::MoveToStartOfDocument,
|
||||
QKeySequence::MoveToEndOfDocument, QKeySequence::SelectNextChar, QKeySequence::SelectPreviousChar,
|
||||
QKeySequence::SelectNextWord, QKeySequence::SelectPreviousWord, QKeySequence::SelectNextLine,
|
||||
QKeySequence::SelectPreviousLine, QKeySequence::SelectStartOfLine, QKeySequence::SelectEndOfLine,
|
||||
QKeySequence::SelectStartOfBlock, QKeySequence::SelectEndOfBlock, QKeySequence::SelectStartOfDocument,
|
||||
QKeySequence::SelectEndOfDocument, QKeySequence::DeleteStartOfWord, QKeySequence::DeleteEndOfWord,
|
||||
QKeySequence::DeleteEndOfLine, QKeySequence::Deselect, QKeySequence::DeleteCompleteLine, QKeySequence::Backspace };
|
||||
|
||||
if (std::any_of(std::begin(acceptedKeys), std::end(acceptedKeys), [event](QKeySequence::StandardKey standardKey) { return event == standardKey; }))
|
||||
{
|
||||
event->accept();
|
||||
return;
|
||||
}
|
||||
|
||||
switch (event->key())
|
||||
{
|
||||
case Qt::Key_Direction_L:
|
||||
case Qt::Key_Direction_R:
|
||||
case Qt::Key_Up:
|
||||
case Qt::Key_Down:
|
||||
case Qt::Key_Left:
|
||||
case Qt::Key_Right:
|
||||
event->accept();
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (!event->text().isEmpty())
|
||||
{
|
||||
event->accept();
|
||||
for (const QChar& character : event->text())
|
||||
{
|
||||
if (!character.isPrint())
|
||||
{
|
||||
event->ignore();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PDFTextEditPseudowidget::keyPressEvent(QWidget* widget, QKeyEvent* event)
|
||||
{
|
||||
Q_UNUSED(widget);
|
||||
|
||||
/*
|
||||
We will support following key sequences:
|
||||
Delete
|
||||
Cut,
|
||||
Copy,
|
||||
Paste,
|
||||
SelectAll,
|
||||
MoveToNextChar,
|
||||
MoveToPreviousChar,
|
||||
MoveToNextWord,
|
||||
MoveToPreviousWord,
|
||||
MoveToNextLine,
|
||||
MoveToPreviousLine,
|
||||
MoveToStartOfLine,
|
||||
MoveToEndOfLine,
|
||||
MoveToStartOfBlock,
|
||||
MoveToEndOfBlock,
|
||||
MoveToStartOfDocument,
|
||||
MoveToEndOfDocument,
|
||||
SelectNextChar,
|
||||
SelectPreviousChar,
|
||||
SelectNextWord,
|
||||
SelectPreviousWord,
|
||||
SelectNextLine,
|
||||
SelectPreviousLine,
|
||||
SelectStartOfLine,
|
||||
SelectEndOfLine,
|
||||
SelectStartOfBlock,
|
||||
SelectEndOfBlock,
|
||||
SelectStartOfDocument,
|
||||
SelectEndOfDocument,
|
||||
DeleteStartOfWord,
|
||||
DeleteEndOfWord,
|
||||
DeleteEndOfLine,
|
||||
Deselect,
|
||||
DeleteCompleteLine,
|
||||
Backspace,
|
||||
* */
|
||||
|
||||
event->accept();
|
||||
|
||||
if (event == QKeySequence::Delete)
|
||||
{
|
||||
performDelete();
|
||||
}
|
||||
else if (event == QKeySequence::Cut)
|
||||
{
|
||||
performCut();
|
||||
}
|
||||
else if (event == QKeySequence::Copy)
|
||||
{
|
||||
performCopy();
|
||||
}
|
||||
else if (event == QKeySequence::Paste)
|
||||
{
|
||||
performPaste();
|
||||
}
|
||||
else if (event == QKeySequence::SelectAll)
|
||||
{
|
||||
setSelection(0, getTextLength());
|
||||
}
|
||||
else if (event == QKeySequence::MoveToNextChar)
|
||||
{
|
||||
setCursorPosition(getCursorCharacterForward(), false);
|
||||
}
|
||||
else if (event == QKeySequence::MoveToPreviousChar)
|
||||
{
|
||||
setCursorPosition(getCursorCharacterBackward(), false);
|
||||
}
|
||||
else if (event == QKeySequence::MoveToNextWord)
|
||||
{
|
||||
setCursorPosition(getCursorWordForward(), false);
|
||||
}
|
||||
else if (event == QKeySequence::MoveToPreviousWord)
|
||||
{
|
||||
setCursorPosition(getCursorWordBackward(), false);
|
||||
}
|
||||
else if (event == QKeySequence::MoveToNextLine)
|
||||
{
|
||||
setCursorPosition(getCursorLineDown(), false);
|
||||
}
|
||||
else if (event == QKeySequence::MoveToPreviousLine)
|
||||
{
|
||||
setCursorPosition(getCursorLineUp(), false);
|
||||
}
|
||||
else if (event == QKeySequence::MoveToStartOfLine || event == QKeySequence::MoveToStartOfBlock)
|
||||
{
|
||||
setCursorPosition(getCursorLineStart(), false);
|
||||
}
|
||||
else if (event == QKeySequence::MoveToEndOfLine || event == QKeySequence::MoveToEndOfBlock)
|
||||
{
|
||||
setCursorPosition(getCursorLineEnd(), false);
|
||||
}
|
||||
else if (event == QKeySequence::MoveToStartOfDocument)
|
||||
{
|
||||
setCursorPosition(getCursorDocumentStart(), false);
|
||||
}
|
||||
else if (event == QKeySequence::MoveToEndOfDocument)
|
||||
{
|
||||
setCursorPosition(getCursorDocumentEnd(), false);
|
||||
}
|
||||
else if (event == QKeySequence::SelectNextChar)
|
||||
{
|
||||
setCursorPosition(getCursorCharacterForward(), true);
|
||||
}
|
||||
else if (event == QKeySequence::SelectPreviousChar)
|
||||
{
|
||||
setCursorPosition(getCursorCharacterBackward(), true);
|
||||
}
|
||||
else if (event == QKeySequence::SelectNextWord)
|
||||
{
|
||||
setCursorPosition(getCursorWordForward(), true);
|
||||
}
|
||||
else if (event == QKeySequence::SelectPreviousWord)
|
||||
{
|
||||
setCursorPosition(getCursorWordBackward(), true);
|
||||
}
|
||||
else if (event == QKeySequence::SelectNextLine)
|
||||
{
|
||||
setCursorPosition(getCursorLineDown(), true);
|
||||
}
|
||||
else if (event == QKeySequence::SelectPreviousLine)
|
||||
{
|
||||
setCursorPosition(getCursorLineUp(), true);
|
||||
}
|
||||
else if (event == QKeySequence::SelectStartOfLine || event == QKeySequence::SelectStartOfBlock)
|
||||
{
|
||||
setCursorPosition(getCursorLineStart(), true);
|
||||
}
|
||||
else if (event == QKeySequence::SelectEndOfLine || event == QKeySequence::SelectEndOfBlock)
|
||||
{
|
||||
setCursorPosition(getCursorLineEnd(), true);
|
||||
}
|
||||
else if (event == QKeySequence::SelectStartOfDocument)
|
||||
{
|
||||
setCursorPosition(getCursorDocumentStart(), true);
|
||||
}
|
||||
else if (event == QKeySequence::SelectEndOfDocument)
|
||||
{
|
||||
setCursorPosition(getCursorDocumentEnd(), true);
|
||||
}
|
||||
else if (event == QKeySequence::DeleteStartOfWord)
|
||||
{
|
||||
if (!isReadonly())
|
||||
{
|
||||
setCursorPosition(getCursorWordBackward(), true);
|
||||
performRemoveSelectedText();
|
||||
}
|
||||
}
|
||||
else if (event == QKeySequence::DeleteEndOfWord)
|
||||
{
|
||||
if (!isReadonly())
|
||||
{
|
||||
setCursorPosition(getCursorWordForward(), true);
|
||||
performRemoveSelectedText();
|
||||
}
|
||||
}
|
||||
else if (event == QKeySequence::DeleteEndOfLine)
|
||||
{
|
||||
if (!isReadonly())
|
||||
{
|
||||
setCursorPosition(getCursorLineEnd(), true);
|
||||
performRemoveSelectedText();
|
||||
}
|
||||
}
|
||||
else if (event == QKeySequence::Deselect)
|
||||
{
|
||||
clearSelection();
|
||||
}
|
||||
else if (event == QKeySequence::DeleteCompleteLine)
|
||||
{
|
||||
if (!isReadonly())
|
||||
{
|
||||
m_selectionStart = getCurrentLineTextStart();
|
||||
m_selectionEnd = getCurrentLineTextEnd();
|
||||
performRemoveSelectedText();
|
||||
}
|
||||
}
|
||||
else if (event == QKeySequence::Backspace || event->key() == Qt::Key_Backspace)
|
||||
{
|
||||
performBackspace();
|
||||
}
|
||||
else if (event->key() == Qt::Key_Direction_L)
|
||||
{
|
||||
QTextOption option = m_textLayout.textOption();
|
||||
option.setTextDirection(Qt::LeftToRight);
|
||||
m_textLayout.setTextOption(qMove(option));
|
||||
updateTextLayout();
|
||||
}
|
||||
else if (event->key() == Qt::Key_Direction_R)
|
||||
{
|
||||
QTextOption option = m_textLayout.textOption();
|
||||
option.setTextDirection(Qt::LeftToRight);
|
||||
m_textLayout.setTextOption(qMove(option));
|
||||
updateTextLayout();
|
||||
}
|
||||
else if (event->key() == Qt::Key_Up)
|
||||
{
|
||||
setCursorPosition(getCursorLineUp(), event->modifiers().testFlag(Qt::ShiftModifier));
|
||||
}
|
||||
else if (event->key() == Qt::Key_Down)
|
||||
{
|
||||
setCursorPosition(getCursorLineDown(), event->modifiers().testFlag(Qt::ShiftModifier));
|
||||
}
|
||||
else if (event->key() == Qt::Key_Left)
|
||||
{
|
||||
const int position = (event->modifiers().testFlag(Qt::ControlModifier)) ? getCursorWordBackward() : getCursorCharacterBackward();
|
||||
setCursorPosition(position, event->modifiers().testFlag(Qt::ShiftModifier));
|
||||
}
|
||||
else if (event->key() == Qt::Key_Right)
|
||||
{
|
||||
const int position = (event->modifiers().testFlag(Qt::ControlModifier)) ? getCursorWordForward() : getCursorCharacterForward();
|
||||
setCursorPosition(position, event->modifiers().testFlag(Qt::ShiftModifier));
|
||||
}
|
||||
else if (isMultiline() && (event->key() == Qt::Key_Enter || event->key() == Qt::Key_Return))
|
||||
{
|
||||
performInsertText(QString::fromUtf16(u"\u2028"));
|
||||
}
|
||||
else
|
||||
{
|
||||
QString text = event->text();
|
||||
if (!text.isEmpty())
|
||||
{
|
||||
performInsertText(text);
|
||||
}
|
||||
else
|
||||
{
|
||||
event->ignore();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PDFTextEditPseudowidget::setSelection(int startPosition, int selectionLength)
|
||||
{
|
||||
if (selectionLength > 0)
|
||||
{
|
||||
// We are selecting to the right
|
||||
m_selectionStart = startPosition;
|
||||
m_selectionEnd = qMin(startPosition + selectionLength, getTextLength());
|
||||
m_positionCursor = m_selectionEnd;
|
||||
}
|
||||
else if (selectionLength < 0)
|
||||
{
|
||||
// We are selecting to the left
|
||||
m_selectionStart = qMax(startPosition + selectionLength, 0);
|
||||
m_selectionEnd = startPosition;
|
||||
m_positionCursor = m_selectionStart;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Clear the selection
|
||||
m_selectionStart = 0;
|
||||
m_selectionEnd = 0;
|
||||
m_positionCursor = startPosition;
|
||||
}
|
||||
}
|
||||
|
||||
void PDFTextEditPseudowidget::setCursorPosition(int position, bool select)
|
||||
{
|
||||
if (select)
|
||||
{
|
||||
const bool isTextSelected = this->isTextSelected();
|
||||
const bool isCursorAtStartOfSelection = isTextSelected && m_selectionStart == m_positionCursor;
|
||||
const bool isCursorAtEndOfSelection = isTextSelected && m_selectionEnd == m_positionCursor;
|
||||
|
||||
// Do we have selected text, and cursor is at the end of selected text?
|
||||
// In this case, we must preserve start of the selection (we are manipulating
|
||||
// with the end of selection.
|
||||
if (isCursorAtEndOfSelection)
|
||||
{
|
||||
m_selectionStart = qMin(m_selectionStart, position);
|
||||
m_selectionEnd = qMax(m_selectionStart, position);
|
||||
}
|
||||
else if (isCursorAtStartOfSelection)
|
||||
{
|
||||
// We must preserve end of the text selection, because we are manipulating
|
||||
// with start of text selection.
|
||||
m_selectionStart = qMin(m_selectionEnd, position);
|
||||
m_selectionEnd = qMax(m_selectionEnd, position);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Otherwise we are manipulating with cursor
|
||||
m_selectionStart = qMin(m_positionCursor, position);
|
||||
m_selectionEnd = qMax(m_positionCursor, position);
|
||||
}
|
||||
}
|
||||
|
||||
// Why we are clearing text selection, even if we doesn't have it?
|
||||
// We can have, for example m_selectionStart == m_selectionEnd == 3,
|
||||
// and we want to have it both zero.
|
||||
if (!select || !isTextSelected())
|
||||
{
|
||||
clearSelection();
|
||||
}
|
||||
|
||||
m_positionCursor = position;
|
||||
}
|
||||
|
||||
void PDFTextEditPseudowidget::setText(const QString& text)
|
||||
{
|
||||
clearSelection();
|
||||
m_editText = text;
|
||||
setCursorPosition(getPositionEnd(), false);
|
||||
updateTextLayout();
|
||||
}
|
||||
|
||||
void PDFTextEditPseudowidget::setAppearance(const PDFAnnotationDefaultAppearance& appearance, Qt::Alignment textAlignment, QRectF rect, int maxTextLength)
|
||||
{
|
||||
// Set appearance
|
||||
qreal fontSize = appearance.getFontSize();
|
||||
if (qFuzzyIsNull(fontSize))
|
||||
{
|
||||
fontSize = rect.height();
|
||||
}
|
||||
|
||||
QFont font(appearance.getFontName());
|
||||
font.setHintingPreference(QFont::PreferNoHinting);
|
||||
font.setPixelSize(qCeil(fontSize));
|
||||
font.setStyleStrategy(QFont::ForceOutline);
|
||||
m_textLayout.setFont(font);
|
||||
|
||||
QTextOption option = m_textLayout.textOption();
|
||||
option.setWrapMode(isMultiline() ? QTextOption::WrapAtWordBoundaryOrAnywhere : QTextOption::NoWrap);
|
||||
option.setAlignment(textAlignment);
|
||||
option.setUseDesignMetrics(true);
|
||||
m_textLayout.setTextOption(option);
|
||||
|
||||
m_textColor = appearance.getFontColor();
|
||||
if (!m_textColor.isValid())
|
||||
{
|
||||
m_textColor = Qt::black;
|
||||
}
|
||||
|
||||
m_maxTextLength = maxTextLength;
|
||||
m_widgetRect = rect;
|
||||
updateTextLayout();
|
||||
}
|
||||
|
||||
void PDFTextEditPseudowidget::setAppearance(const QFont& font,
|
||||
Qt::Alignment textAlignment,
|
||||
QRectF rect,
|
||||
int maxTextLength,
|
||||
QColor textColor)
|
||||
{
|
||||
// Set appearance
|
||||
m_textLayout.setFont(font);
|
||||
|
||||
QTextOption option = m_textLayout.textOption();
|
||||
option.setWrapMode(isMultiline() ? QTextOption::WrapAtWordBoundaryOrAnywhere : QTextOption::NoWrap);
|
||||
option.setAlignment(textAlignment);
|
||||
option.setUseDesignMetrics(true);
|
||||
m_textLayout.setTextOption(option);
|
||||
|
||||
m_textColor = textColor;
|
||||
if (!m_textColor.isValid())
|
||||
{
|
||||
m_textColor = Qt::black;
|
||||
}
|
||||
|
||||
m_maxTextLength = maxTextLength;
|
||||
m_widgetRect = rect;
|
||||
updateTextLayout();
|
||||
}
|
||||
|
||||
void PDFTextEditPseudowidget::performCut()
|
||||
{
|
||||
if (isReadonly())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
performCopy();
|
||||
performRemoveSelectedText();
|
||||
}
|
||||
|
||||
void PDFTextEditPseudowidget::performCopy()
|
||||
{
|
||||
if (isTextSelected() && !isPassword())
|
||||
{
|
||||
QApplication::clipboard()->setText(getSelectedText(), QClipboard::Clipboard);
|
||||
}
|
||||
}
|
||||
|
||||
void PDFTextEditPseudowidget::performPaste()
|
||||
{
|
||||
// We always insert text, even if it is empty. Because we want
|
||||
// to erase selected text.
|
||||
performInsertText(QApplication::clipboard()->text(QClipboard::Clipboard));
|
||||
}
|
||||
|
||||
void PDFTextEditPseudowidget::performClear()
|
||||
{
|
||||
if (isReadonly())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
performSelectAll();
|
||||
performRemoveSelectedText();
|
||||
}
|
||||
|
||||
void PDFTextEditPseudowidget::performSelectAll()
|
||||
{
|
||||
m_selectionStart = getPositionStart();
|
||||
m_selectionEnd = getPositionEnd();
|
||||
}
|
||||
|
||||
void PDFTextEditPseudowidget::performBackspace()
|
||||
{
|
||||
if (isReadonly())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// If we have selection, then delete selected text. If we do not have
|
||||
// selection, then we delete previous character.
|
||||
if (!isTextSelected())
|
||||
{
|
||||
setCursorPosition(m_textLayout.previousCursorPosition(m_positionCursor, QTextLayout::SkipCharacters), true);
|
||||
}
|
||||
|
||||
performRemoveSelectedText();
|
||||
}
|
||||
|
||||
void PDFTextEditPseudowidget::performDelete()
|
||||
{
|
||||
if (isReadonly())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// If we have selection, then delete selected text. If we do not have
|
||||
// selection, then we delete previous character.
|
||||
if (!isTextSelected())
|
||||
{
|
||||
setCursorPosition(m_textLayout.nextCursorPosition(m_positionCursor, QTextLayout::SkipCharacters), true);
|
||||
}
|
||||
|
||||
performRemoveSelectedText();
|
||||
}
|
||||
|
||||
void PDFTextEditPseudowidget::performRemoveSelectedText()
|
||||
{
|
||||
if (isTextSelected())
|
||||
{
|
||||
m_editText.remove(m_selectionStart, getSelectionLength());
|
||||
setCursorPosition(m_selectionStart, false);
|
||||
clearSelection();
|
||||
updateTextLayout();
|
||||
}
|
||||
}
|
||||
|
||||
void PDFTextEditPseudowidget::performInsertText(const QString& text)
|
||||
{
|
||||
if (isReadonly())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Insert text at the cursor
|
||||
performRemoveSelectedText();
|
||||
m_editText.insert(m_positionCursor, text);
|
||||
setCursorPosition(m_positionCursor + text.length(), false);
|
||||
updateTextLayout();
|
||||
}
|
||||
|
||||
QTransform PDFTextEditPseudowidget::createTextBoxTransformMatrix(bool edit) const
|
||||
{
|
||||
QTransform matrix;
|
||||
|
||||
matrix.translate(m_widgetRect.left(), m_widgetRect.bottom());
|
||||
matrix.scale(1.0, -1.0);
|
||||
|
||||
if (edit && !isComb() && m_textLayout.isValidCursorPosition(m_positionCursor))
|
||||
{
|
||||
// Jakub Melka: we must scroll the control, so cursor is always
|
||||
// visible in the widget area. If we are not editing, this not the
|
||||
// case, because we always show the text from the beginning.
|
||||
|
||||
const QTextLine line = m_textLayout.lineForTextPosition(m_positionCursor);
|
||||
if (line.isValid())
|
||||
{
|
||||
const qreal xCursorPosition = line.cursorToX(m_positionCursor);
|
||||
if (xCursorPosition >= m_widgetRect.width())
|
||||
{
|
||||
const qreal delta = xCursorPosition - m_widgetRect.width();
|
||||
matrix.translate(-delta, 0.0);
|
||||
}
|
||||
|
||||
// Check, if we aren't outside of y position
|
||||
const qreal lineSpacing = line.leadingIncluded() ? line.height() : line.leading() + line.height();
|
||||
const qreal lineBottom = lineSpacing * (line.lineNumber() + 1);
|
||||
|
||||
if (lineBottom >= m_widgetRect.height())
|
||||
{
|
||||
const qreal delta = lineBottom - m_widgetRect.height();
|
||||
matrix.translate(0.0, -delta);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!isMultiline() && !isComb())
|
||||
{
|
||||
// If text is single line, then adjust text position to the vertical center
|
||||
QTextLine textLine = m_textLayout.lineAt(0);
|
||||
if (textLine.isValid())
|
||||
{
|
||||
const qreal lineSpacing = textLine.leadingIncluded() ? textLine.height() : textLine.leading() + textLine.height();
|
||||
const qreal textBoxHeight = m_widgetRect.height();
|
||||
|
||||
if (lineSpacing < textBoxHeight)
|
||||
{
|
||||
const qreal delta = (textBoxHeight - lineSpacing) * 0.5;
|
||||
matrix.translate(0.0, delta);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return matrix;
|
||||
}
|
||||
|
||||
std::vector<int> PDFTextEditPseudowidget::getCursorPositions() const
|
||||
{
|
||||
std::vector<int> result;
|
||||
result.reserve(m_editText.length());
|
||||
result.push_back(0);
|
||||
|
||||
int currentPos = 0;
|
||||
while (currentPos < m_editText.length())
|
||||
{
|
||||
currentPos = m_textLayout.nextCursorPosition(currentPos, QTextLayout::SkipCharacters);
|
||||
result.push_back(currentPos);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void PDFTextEditPseudowidget::draw(AnnotationDrawParameters& parameters, bool edit) const
|
||||
{
|
||||
pdf::PDFPainterStateGuard guard(parameters.painter);
|
||||
|
||||
if (parameters.annotation)
|
||||
{
|
||||
parameters.boundingRectangle = parameters.annotation->getRectangle();
|
||||
}
|
||||
|
||||
QPalette palette = QApplication::palette();
|
||||
QPainter* painter = parameters.painter;
|
||||
|
||||
if (edit)
|
||||
{
|
||||
pdf::PDFPainterStateGuard guard2(painter);
|
||||
painter->setPen(parameters.colorConvertor.convert(Qt::black, false, true));
|
||||
painter->setBrush(Qt::NoBrush);
|
||||
painter->drawRect(parameters.boundingRectangle);
|
||||
}
|
||||
|
||||
painter->setClipRect(parameters.boundingRectangle, Qt::IntersectClip);
|
||||
painter->setWorldTransform(QTransform(createTextBoxTransformMatrix(edit)), true);
|
||||
painter->setPen(parameters.colorConvertor.convert(Qt::black, false, true));
|
||||
|
||||
if (isComb())
|
||||
{
|
||||
const qreal combCount = qMax(m_maxTextLength, 1);
|
||||
qreal combWidth = m_widgetRect.width() / combCount;
|
||||
QRectF combRect(0.0, 0.0, combWidth, m_widgetRect.height());
|
||||
painter->setFont(m_textLayout.font());
|
||||
|
||||
QColor textColor = parameters.colorConvertor.convert(m_textColor, false, true);
|
||||
QColor highlightTextColor = parameters.colorConvertor.convert(palette.color(QPalette::HighlightedText), false, true);
|
||||
QColor highlightColor = parameters.colorConvertor.convert(palette.color(QPalette::Highlight), false, false);
|
||||
|
||||
std::vector<int> positions = getCursorPositions();
|
||||
for (size_t i = 1; i < positions.size(); ++i)
|
||||
{
|
||||
if (positions[i - 1] >= m_selectionStart && positions[i] - 1 < m_selectionEnd)
|
||||
{
|
||||
// We are in text selection
|
||||
painter->fillRect(combRect, highlightColor);
|
||||
painter->setPen(highlightTextColor);
|
||||
}
|
||||
else
|
||||
{
|
||||
// We are not in text selection
|
||||
painter->setPen(textColor);
|
||||
}
|
||||
|
||||
int length = positions[i] - positions[i - 1];
|
||||
QString text = m_displayText.mid(positions[i - 1], length);
|
||||
painter->drawText(combRect, Qt::AlignCenter, text);
|
||||
|
||||
// Draw the cursor?
|
||||
if (edit && m_positionCursor >= positions[i - 1] && m_positionCursor < positions[i])
|
||||
{
|
||||
QRectF cursorRect(combRect.left(), combRect.bottom() - 1, combRect.width(), 1);
|
||||
painter->fillRect(cursorRect, textColor);
|
||||
}
|
||||
|
||||
combRect.translate(combWidth, 0.0);
|
||||
}
|
||||
|
||||
// Draw the cursor onto next unfilled cell?
|
||||
if (edit && m_positionCursor == getPositionEnd())
|
||||
{
|
||||
QRectF cursorRect(combRect.left(), combRect.bottom() - 1, combRect.width(), 1);
|
||||
painter->fillRect(cursorRect, textColor);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
QVector<QTextLayout::FormatRange> selections;
|
||||
|
||||
QTextLayout::FormatRange defaultFormat;
|
||||
defaultFormat.start = getPositionStart();
|
||||
defaultFormat.length = getTextLength();
|
||||
defaultFormat.format.clearBackground();
|
||||
defaultFormat.format.setForeground(QBrush(parameters.colorConvertor.convert(m_textColor, false, true), Qt::SolidPattern));
|
||||
|
||||
// If we are editing, draw selections
|
||||
if (edit && isTextSelected())
|
||||
{
|
||||
QTextLayout::FormatRange before = defaultFormat;
|
||||
QTextLayout::FormatRange after = defaultFormat;
|
||||
|
||||
before.start = getPositionStart();
|
||||
before.length = m_selectionStart;
|
||||
after.start = m_selectionEnd;
|
||||
after.length = getTextLength() - m_selectionEnd;
|
||||
|
||||
QTextLayout::FormatRange selectedFormat = defaultFormat;
|
||||
selectedFormat.start = m_selectionStart;
|
||||
selectedFormat.length = getSelectionLength();
|
||||
selectedFormat.format.setForeground(QBrush(parameters.colorConvertor.convert(palette.color(QPalette::HighlightedText), false, true), Qt::SolidPattern));
|
||||
selectedFormat.format.setBackground(QBrush(parameters.colorConvertor.convert(palette.color(QPalette::Highlight), false, false), Qt::SolidPattern));
|
||||
|
||||
selections = { before, selectedFormat, after};
|
||||
}
|
||||
else
|
||||
{
|
||||
selections.push_back(defaultFormat);
|
||||
}
|
||||
|
||||
// Draw text
|
||||
m_textLayout.draw(painter, QPointF(0.0, 0.0), selections, QRectF());
|
||||
|
||||
// If we are editing, also draw text
|
||||
if (edit && !isReadonly())
|
||||
{
|
||||
m_textLayout.drawCursor(painter, QPointF(0.0, 0.0), m_positionCursor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int PDFTextEditPseudowidget::getCursorPositionFromWidgetPosition(const QPointF& point, bool edit) const
|
||||
{
|
||||
QTransform textBoxSpaceToPageSpace = createTextBoxTransformMatrix(edit);
|
||||
QTransform pageSpaceToTextBoxSpace = textBoxSpaceToPageSpace.inverted();
|
||||
|
||||
QPointF textBoxPoint = pageSpaceToTextBoxSpace.map(point);
|
||||
|
||||
if (isComb())
|
||||
{
|
||||
// If it is comb, then characters are spaced equidistantly
|
||||
const qreal x = qBound(0.0, textBoxPoint.x(), m_widgetRect.width());
|
||||
const size_t position = qFloor(x * qreal(m_maxTextLength) / qreal(m_widgetRect.width()));
|
||||
std::vector<int> positions = getCursorPositions();
|
||||
if (position < positions.size())
|
||||
{
|
||||
return positions[position];
|
||||
}
|
||||
|
||||
return positions.back();
|
||||
}
|
||||
else if (m_textLayout.lineCount() > 0)
|
||||
{
|
||||
QTextLine line;
|
||||
qreal yPos = 0.0;
|
||||
|
||||
// Find line under cursor
|
||||
for (int i = 0; i < m_textLayout.lineCount(); ++i)
|
||||
{
|
||||
QTextLine currentLine = m_textLayout.lineAt(i);
|
||||
const qreal lineSpacing = currentLine.leadingIncluded() ? currentLine.height() : currentLine.leading() + currentLine.height();
|
||||
const qreal yNextPos = yPos + lineSpacing;
|
||||
|
||||
if (textBoxPoint.y() >= yPos && textBoxPoint.y() < yNextPos)
|
||||
{
|
||||
line = currentLine;
|
||||
break;
|
||||
}
|
||||
|
||||
yPos = yNextPos;
|
||||
}
|
||||
|
||||
// If no line is found, select last line
|
||||
if (!line.isValid())
|
||||
{
|
||||
if (textBoxPoint.y() < 0.0)
|
||||
{
|
||||
line = m_textLayout.lineAt(0);
|
||||
}
|
||||
else
|
||||
{
|
||||
line = m_textLayout.lineAt(m_textLayout.lineCount() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
return line.xToCursor(textBoxPoint.x(), QTextLine::CursorBetweenCharacters);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void PDFTextEditPseudowidget::updateTextLayout()
|
||||
{
|
||||
// Prepare display text
|
||||
if (isPassword())
|
||||
{
|
||||
m_displayText.resize(m_editText.length(), m_passwordReplacementCharacter);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_displayText = m_editText;
|
||||
}
|
||||
|
||||
// Perform text layout
|
||||
m_textLayout.clearLayout();
|
||||
m_textLayout.setText(m_displayText);
|
||||
m_textLayout.beginLayout();
|
||||
|
||||
QPointF textLinePosition(0.0, 0.0);
|
||||
|
||||
while (true)
|
||||
{
|
||||
QTextLine textLine = m_textLayout.createLine();
|
||||
if (!textLine.isValid())
|
||||
{
|
||||
// We are finished with layout
|
||||
break;
|
||||
}
|
||||
|
||||
textLinePosition.ry() += textLine.leading();
|
||||
textLine.setLineWidth(m_widgetRect.width());
|
||||
textLine.setPosition(textLinePosition);
|
||||
textLinePosition.ry() += textLine.height();
|
||||
}
|
||||
m_textLayout.endLayout();
|
||||
|
||||
// Check length
|
||||
if (m_maxTextLength > 0)
|
||||
{
|
||||
int length = 0;
|
||||
int currentPos = 0;
|
||||
|
||||
while (currentPos < m_editText.length())
|
||||
{
|
||||
currentPos = m_textLayout.nextCursorPosition(currentPos, QTextLayout::SkipCharacters);
|
||||
++length;
|
||||
|
||||
if (length == m_maxTextLength)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (currentPos < m_editText.length())
|
||||
{
|
||||
m_editText = m_editText.left(currentPos);
|
||||
m_positionCursor = qBound(getPositionStart(), m_positionCursor, getPositionEnd());
|
||||
updateTextLayout();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int PDFTextEditPseudowidget::getSingleStepForward() const
|
||||
{
|
||||
// If direction is right-to-left, then move backward (because
|
||||
// text is painted from right to left.
|
||||
return (m_textLayout.textOption().textDirection() == Qt::RightToLeft) ? -1 : 1;
|
||||
}
|
||||
|
||||
int PDFTextEditPseudowidget::getNextPrevCursorPosition(int referencePosition, int steps, QTextLayout::CursorMode mode) const
|
||||
{
|
||||
int cursor = referencePosition;
|
||||
|
||||
if (steps > 0)
|
||||
{
|
||||
for (int i = 0; i < steps; ++i)
|
||||
{
|
||||
cursor = m_textLayout.nextCursorPosition(cursor, mode);
|
||||
}
|
||||
}
|
||||
else if (steps < 0)
|
||||
{
|
||||
for (int i = 0; i < -steps; ++i)
|
||||
{
|
||||
cursor = m_textLayout.previousCursorPosition(cursor, mode);
|
||||
}
|
||||
}
|
||||
|
||||
return cursor;
|
||||
}
|
||||
|
||||
int PDFTextEditPseudowidget::getCurrentLineTextStart() const
|
||||
{
|
||||
return m_textLayout.lineForTextPosition(m_positionCursor).textStart();
|
||||
}
|
||||
|
||||
int PDFTextEditPseudowidget::getCurrentLineTextEnd() const
|
||||
{
|
||||
QTextLine textLine = m_textLayout.lineForTextPosition(m_positionCursor);
|
||||
return textLine.textStart() + textLine.textLength();
|
||||
}
|
||||
|
||||
int PDFTextEditPseudowidget::getCursorLineUp() const
|
||||
{
|
||||
QTextLine line = m_textLayout.lineForTextPosition(m_positionCursor);
|
||||
const int lineIndex = line.lineNumber() - 1;
|
||||
|
||||
if (lineIndex >= 0)
|
||||
{
|
||||
QTextLine upLine = m_textLayout.lineAt(lineIndex);
|
||||
return upLine.xToCursor(line.cursorToX(m_positionCursor), QTextLine::CursorBetweenCharacters);
|
||||
}
|
||||
|
||||
return m_positionCursor;
|
||||
}
|
||||
|
||||
int PDFTextEditPseudowidget::getCursorLineDown() const
|
||||
{
|
||||
QTextLine line = m_textLayout.lineForTextPosition(m_positionCursor);
|
||||
const int lineIndex = line.lineNumber() + 1;
|
||||
|
||||
if (lineIndex < m_textLayout.lineCount())
|
||||
{
|
||||
QTextLine downLine = m_textLayout.lineAt(lineIndex);
|
||||
return downLine.xToCursor(line.cursorToX(m_positionCursor), QTextLine::CursorBetweenCharacters);
|
||||
}
|
||||
|
||||
return m_positionCursor;
|
||||
}
|
||||
|
||||
} // namespace pdf
|
@@ -1,217 +0,0 @@
|
||||
// Copyright (C) 2022 Jakub Melka
|
||||
//
|
||||
// This file is part of PDF4QT.
|
||||
//
|
||||
// PDF4QT is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// with the written consent of the copyright owner, any later version.
|
||||
//
|
||||
// PDF4QT is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with PDF4QT. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
#ifndef PDFTEXTEDITPSEUDOWIDGET_H
|
||||
#define PDFTEXTEDITPSEUDOWIDGET_H
|
||||
|
||||
#include "pdfglobal.h"
|
||||
#include "pdfform.h"
|
||||
|
||||
#include <QRectF>
|
||||
#include <QColor>
|
||||
|
||||
class QWidget;
|
||||
class QKeyEvent;
|
||||
|
||||
namespace pdf
|
||||
{
|
||||
|
||||
/// "Pseudo" widget, which is emulating text editor, which can be single line, or multiline.
|
||||
/// Passwords can also be edited and editor can be read only.
|
||||
class PDFTextEditPseudowidget
|
||||
{
|
||||
public:
|
||||
explicit PDFTextEditPseudowidget(PDFFormField::FieldFlags flags);
|
||||
|
||||
void shortcutOverrideEvent(QWidget* widget, QKeyEvent* event);
|
||||
void keyPressEvent(QWidget* widget, QKeyEvent* event);
|
||||
|
||||
inline bool isReadonly() const { return m_flags.testFlag(PDFFormField::ReadOnly); }
|
||||
inline bool isMultiline() const { return m_flags.testFlag(PDFFormField::Multiline); }
|
||||
inline bool isPassword() const { return m_flags.testFlag(PDFFormField::Password); }
|
||||
inline bool isFileSelect() const { return m_flags.testFlag(PDFFormField::FileSelect); }
|
||||
inline bool isComb() const { return m_flags.testFlag(PDFFormField::Comb); }
|
||||
|
||||
inline bool isEmpty() const { return m_editText.isEmpty(); }
|
||||
inline bool isTextSelected() const { return !isEmpty() && getSelectionLength() > 0; }
|
||||
inline bool isWholeTextSelected() const { return !isEmpty() && getSelectionLength() == m_editText.length(); }
|
||||
|
||||
inline int getTextLength() const { return m_editText.length(); }
|
||||
inline int getSelectionLength() const { return m_selectionEnd - m_selectionStart; }
|
||||
|
||||
inline int getPositionCursor() const { return m_positionCursor; }
|
||||
inline int getPositionStart() const { return 0; }
|
||||
inline int getPositionEnd() const { return getTextLength(); }
|
||||
|
||||
inline void clearSelection() { m_selectionStart = m_selectionEnd = 0; }
|
||||
|
||||
inline const QString& getText() const { return m_editText; }
|
||||
inline QString getSelectedText() const { return m_editText.mid(m_selectionStart, getSelectionLength()); }
|
||||
|
||||
/// Sets (updates) text selection
|
||||
/// \param startPosition From where we are selecting text
|
||||
/// \param selectionLength Selection length (positive - to the right, negative - to the left)
|
||||
void setSelection(int startPosition, int selectionLength);
|
||||
|
||||
/// Moves cursor position. It behaves as usual in text boxes,
|
||||
/// when user moves the cursor. If \p select is true, then
|
||||
/// selection is updated.
|
||||
/// \param position New position of the cursor
|
||||
/// \param select Select text when moving the cursor?
|
||||
void setCursorPosition(int position, bool select);
|
||||
|
||||
/// Sets text content of the widget. This functions sets the text,
|
||||
/// even if widget is readonly.
|
||||
/// \param text Text to be set
|
||||
void setText(const QString& text);
|
||||
|
||||
/// Sets widget appearance, such as font, font size, color, text alignment,
|
||||
/// and rectangle, in which widget resides on page (in page coordinates)
|
||||
/// \param appearance Appearance
|
||||
/// \param textAlignment Text alignment
|
||||
/// \param rect Widget rectangle in page coordinates
|
||||
/// \param maxTextLength Maximal text length
|
||||
void setAppearance(const PDFAnnotationDefaultAppearance& appearance,
|
||||
Qt::Alignment textAlignment,
|
||||
QRectF rect,
|
||||
int maxTextLength);
|
||||
|
||||
/// Sets widget appearance, such as font, font size, color, text alignment,
|
||||
/// and rectangle, in which widget resides on page (in page coordinates)
|
||||
/// \param font Font
|
||||
/// \param textAlignment Text alignment
|
||||
/// \param rect Widget rectangle in page coordinates
|
||||
/// \param maxTextLength Maximal text length
|
||||
/// \param textColor Color
|
||||
void setAppearance(const QFont& font,
|
||||
Qt::Alignment textAlignment,
|
||||
QRectF rect,
|
||||
int maxTextLength,
|
||||
QColor textColor);
|
||||
|
||||
void performCut();
|
||||
void performCopy();
|
||||
void performPaste();
|
||||
void performClear();
|
||||
void performSelectAll();
|
||||
void performBackspace();
|
||||
void performDelete();
|
||||
void performRemoveSelectedText();
|
||||
void performInsertText(const QString& text);
|
||||
|
||||
/// Draw text edit using given parameters
|
||||
/// \param parameters Parameters
|
||||
void draw(AnnotationDrawParameters& parameters, bool edit) const;
|
||||
|
||||
/// Returns valid cursor position retrieved from position in the widget.
|
||||
/// \param point Point in page coordinate space
|
||||
/// \param edit Are we using edit transformations?
|
||||
/// \returns Cursor position
|
||||
int getCursorPositionFromWidgetPosition(const QPointF& point, bool edit) const;
|
||||
|
||||
inline int getCursorForward(QTextLayout::CursorMode mode) const { return getNextPrevCursorPosition(getSingleStepForward(), mode); }
|
||||
inline int getCursorBackward(QTextLayout::CursorMode mode) const { return getNextPrevCursorPosition(getSingleStepBackward(), mode); }
|
||||
inline int getCursorCharacterForward() const { return getCursorForward(QTextLayout::SkipCharacters); }
|
||||
inline int getCursorCharacterBackward() const { return getCursorBackward(QTextLayout::SkipCharacters); }
|
||||
inline int getCursorWordForward() const { return getCursorForward(QTextLayout::SkipWords); }
|
||||
inline int getCursorWordBackward() const { return getCursorBackward(QTextLayout::SkipWords); }
|
||||
inline int getCursorDocumentStart() const { return (getSingleStepForward() > 0) ? getPositionStart() : getPositionEnd(); }
|
||||
inline int getCursorDocumentEnd() const { return (getSingleStepForward() > 0) ? getPositionEnd() : getPositionStart(); }
|
||||
inline int getCursorLineStart() const { return (getSingleStepForward() > 0) ? getCurrentLineTextStart() : getCurrentLineTextEnd(); }
|
||||
inline int getCursorLineEnd() const { return (getSingleStepForward() > 0) ? getCurrentLineTextEnd() : getCurrentLineTextStart(); }
|
||||
inline int getCursorNextLine() const { return getCurrentLineTextEnd(); }
|
||||
inline int getCursorPreviousLine() const { return getNextPrevCursorPosition(getCurrentLineTextStart(), -1, QTextLayout::SkipCharacters); }
|
||||
|
||||
const QRectF& getWidgetRect() const { return m_widgetRect; }
|
||||
QFont getFont() const { return m_textLayout.font(); }
|
||||
QColor getFontColor() const { return m_textColor; }
|
||||
|
||||
private:
|
||||
/// This function does following things:
|
||||
/// 1) Clamps edit text to fit maximum length
|
||||
/// 2) Creates display string from edit string
|
||||
/// 3) Updates text layout
|
||||
void updateTextLayout();
|
||||
|
||||
/// Returns single step forward, which is determined
|
||||
/// by cursor move style and layout direction.
|
||||
int getSingleStepForward() const;
|
||||
|
||||
/// Returns single step backward, which is determined
|
||||
/// by cursor move style and layout direction.
|
||||
int getSingleStepBackward() const { return -getSingleStepForward(); }
|
||||
|
||||
/// Returns next/previous position, by number of steps,
|
||||
/// using given cursor mode (skipping characters or whole words).
|
||||
/// \param steps Number of steps to proceed (can be negative number)
|
||||
/// \param mode Skip mode - letters or words?
|
||||
int getNextPrevCursorPosition(int steps, QTextLayout::CursorMode mode) const { return getNextPrevCursorPosition(m_positionCursor, steps, mode); }
|
||||
|
||||
/// Returns next/previous position from reference cursor position, by number of steps,
|
||||
/// using given cursor mode (skipping characters or whole words).
|
||||
/// \param referencePosition Reference cursor position
|
||||
/// \param steps Number of steps to proceed (can be negative number)
|
||||
/// \param mode Skip mode - letters or words?
|
||||
int getNextPrevCursorPosition(int referencePosition, int steps, QTextLayout::CursorMode mode) const;
|
||||
|
||||
/// Returns current line text start position
|
||||
int getCurrentLineTextStart() const;
|
||||
|
||||
/// Returns current line text end position
|
||||
int getCurrentLineTextEnd() const;
|
||||
|
||||
/// Creates text box transform matrix, which transforms from
|
||||
/// widget space to page space.
|
||||
/// \param edit Create matrix for text editing?
|
||||
QTransform createTextBoxTransformMatrix(bool edit) const;
|
||||
|
||||
/// Returns vector of cursor positions (which may be not equal
|
||||
/// to edit string length, because edit string can contain surrogates,
|
||||
/// or graphemes, which are single glyphs, but represented by more
|
||||
/// 16-bit unicode codes).
|
||||
std::vector<int> getCursorPositions() const;
|
||||
|
||||
int getCursorLineUp() const;
|
||||
int getCursorLineDown() const;
|
||||
|
||||
PDFFormField::FieldFlags m_flags;
|
||||
|
||||
/// Text edited by the user
|
||||
QString m_editText;
|
||||
|
||||
/// Text, which is displayed. It can differ from text
|
||||
/// edited by user, in case password is being entered.
|
||||
QString m_displayText;
|
||||
|
||||
/// Text layout
|
||||
QTextLayout m_textLayout;
|
||||
|
||||
/// Character for displaying passwords
|
||||
QChar m_passwordReplacementCharacter;
|
||||
|
||||
int m_selectionStart;
|
||||
int m_selectionEnd;
|
||||
int m_positionCursor;
|
||||
int m_maxTextLength;
|
||||
|
||||
QRectF m_widgetRect;
|
||||
QColor m_textColor;
|
||||
};
|
||||
|
||||
} // namespace pdf
|
||||
|
||||
#endif // PDFTEXTEDITPSEUDOWIDGET_H
|
Reference in New Issue
Block a user