diff --git a/src/editor/dialogs/gameinformationdialog.cpp b/src/editor/dialogs/gameinformationdialog.cpp index 0bb4fc3..8ae20de 100644 --- a/src/editor/dialogs/gameinformationdialog.cpp +++ b/src/editor/dialogs/gameinformationdialog.cpp @@ -1,18 +1,992 @@ #include "gameinformationdialog.h" #include "ui_gameinformationdialog.h" -GameInformationDialog::GameInformationDialog(QWidget *parent) : - QDialog{parent}, - m_ui{std::make_unique()} -{ - m_ui->setupUi(this); - -#ifdef Q_OS_LINUX - setWindowFlags((windowFlags() & ~Qt::Dialog) | Qt::Window); +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if defined(QT_PRINTSUPPORT_LIB) +#include +#if QT_CONFIG(printer) +#if QT_CONFIG(printdialog) +#include +#endif +#include +#if QT_CONFIG(printpreviewdialog) +#include +#endif +#endif +#endif +#if QT_CONFIG(whatsthis) +#include +#endif + +#ifdef Q_OS_MAC +const QString rsrcPath = ":/images/mac"; +#else +const QString rsrcPath = ":/images/win"; +#endif + +GameInformationDialog::GameInformationDialog(QWidget *parent) : + QMainWindow{parent} +{ +#ifdef Q_OS_MACOS + setUnifiedTitleAndToolBarOnMac(true); +#endif + setWindowTitle(QCoreApplication::applicationName()); + + textEdit = new QTextEdit{this}; + connect(textEdit, &QTextEdit::currentCharFormatChanged, + this, &GameInformationDialog::currentCharFormatChanged); + connect(textEdit, &QTextEdit::cursorPositionChanged, + this, &GameInformationDialog::cursorPositionChanged); + setCentralWidget(textEdit); + + setToolButtonStyle(Qt::ToolButtonFollowStyle); + setupFileActions(); + setupEditActions(); + setupTextActions(); + + { + QMenu *helpMenu = menuBar()->addMenu(tr("Help")); + helpMenu->addAction(tr("About"), this, &GameInformationDialog::about); + helpMenu->addAction(tr("About &Qt"), qApp, &QApplication::aboutQt); + } + + QFont textFont{"Helvetica"}; + textFont.setStyleHint(QFont::SansSerif); + textEdit->setFont(textFont); + fontChanged(textEdit->font()); + colorChanged(textEdit->textColor()); + alignmentChanged(textEdit->alignment()); + + connect(textEdit->document(), &QTextDocument::modificationChanged, + actionSave, &QAction::setEnabled); + connect(textEdit->document(), &QTextDocument::modificationChanged, + this, &QWidget::setWindowModified); + connect(textEdit->document(), &QTextDocument::undoAvailable, + actionUndo, &QAction::setEnabled); + connect(textEdit->document(), &QTextDocument::redoAvailable, + actionRedo, &QAction::setEnabled); + + setWindowModified(textEdit->document()->isModified()); + actionSave->setEnabled(textEdit->document()->isModified()); + actionUndo->setEnabled(textEdit->document()->isUndoAvailable()); + actionRedo->setEnabled(textEdit->document()->isRedoAvailable()); + +#ifndef QT_NO_CLIPBOARD + actionCut->setEnabled(false); + connect(textEdit, &QTextEdit::copyAvailable, actionCut, &QAction::setEnabled); + actionCopy->setEnabled(false); + connect(textEdit, &QTextEdit::copyAvailable, actionCopy, &QAction::setEnabled); + + connect(QApplication::clipboard(), &QClipboard::dataChanged, this, &GameInformationDialog::clipboardDataChanged); +#endif + + textEdit->setFocus(); + setCurrentFileName(QString{}); + +#ifdef Q_OS_MACOS + // Use dark text on light background on macOS, also in dark mode. + QPalette pal = textEdit->palette(); + pal.setColor(QPalette::Base, QColor(Qt::white)); + pal.setColor(QPalette::Text, QColor(Qt::black)); + textEdit->setPalette(pal); #endif - setWindowFlag(Qt::WindowMinimizeButtonHint); - setWindowFlag(Qt::WindowMaximizeButtonHint); - setWindowFlag(Qt::WindowCloseButtonHint); } -GameInformationDialog::~GameInformationDialog() = default; +void GameInformationDialog::closeEvent(QCloseEvent *e) +{ +#if QT_CONFIG(whatsthis) + if (isModal() && QWhatsThis::inWhatsThisMode()) + QWhatsThis::leaveWhatsThisMode(); +#endif + + if (isVisible()) { + QPointer that = this; + reject(); + if (that && isVisible()) + e->ignore(); + } else { + e->accept(); + } +} + +void GameInformationDialog::setupFileActions() +{ + QToolBar *tb = addToolBar(tr("File Actions")); + QMenu *menu = menuBar()->addMenu(tr("&File")); + + const QIcon newIcon = QIcon::fromTheme("document-new", QIcon{rsrcPath + "/filenew.png"}); + QAction *a = menu->addAction(newIcon, tr("&New"), this, &GameInformationDialog::fileNew); + tb->addAction(a); + a->setPriority(QAction::LowPriority); + a->setShortcut(QKeySequence::New); + + const QIcon openIcon = QIcon::fromTheme("document-open", QIcon(rsrcPath + "/fileopen.png")); + a = menu->addAction(openIcon, tr("&Open..."), this, &GameInformationDialog::fileOpen); + a->setShortcut(QKeySequence::Open); + tb->addAction(a); + + menu->addSeparator(); + + const QIcon saveIcon = QIcon::fromTheme("document-save", QIcon(rsrcPath + "/filesave.png")); + actionSave = menu->addAction(saveIcon, tr("&Save"), this, &GameInformationDialog::fileSave); + actionSave->setShortcut(QKeySequence::Save); + actionSave->setEnabled(false); + tb->addAction(actionSave); + + a = menu->addAction(tr("Save &As..."), this, &GameInformationDialog::fileSaveAs); + a->setPriority(QAction::LowPriority); + menu->addSeparator(); + +#if defined(QT_PRINTSUPPORT_LIB) && QT_CONFIG(printer) + const QIcon printIcon = QIcon::fromTheme("document-print", QIcon(rsrcPath + "/fileprint.png")); + a = menu->addAction(printIcon, tr("&Print..."), this, &GameInformationDialog::filePrint); + a->setPriority(QAction::LowPriority); + a->setShortcut(QKeySequence::Print); + tb->addAction(a); + + const QIcon filePrintIcon = QIcon::fromTheme("fileprint", QIcon(rsrcPath + "/fileprint.png")); + menu->addAction(filePrintIcon, tr("Print Preview..."), this, &GameInformationDialog::filePrintPreview); + + const QIcon exportPdfIcon = QIcon::fromTheme("exportpdf", QIcon(rsrcPath + "/exportpdf.png")); + a = menu->addAction(exportPdfIcon, tr("&Export PDF..."), this, &GameInformationDialog::filePrintPdf); + a->setPriority(QAction::LowPriority); + a->setShortcut(Qt::CTRL + Qt::Key_D); + tb->addAction(a); + + menu->addSeparator(); +#endif + + a = menu->addAction(tr("&Quit"), this, &QWidget::close); + a->setShortcut(QKeySequence::Quit); +} + +void GameInformationDialog::setupEditActions() +{ + QToolBar *tb = addToolBar(tr("Edit Actions")); + QMenu *menu = menuBar()->addMenu(tr("&Edit")); + + const QIcon undoIcon = QIcon::fromTheme("edit-undo", QIcon(rsrcPath + "/editundo.png")); + actionUndo = menu->addAction(undoIcon, tr("&Undo"), textEdit, &QTextEdit::undo); + actionUndo->setShortcut(QKeySequence::Undo); + tb->addAction(actionUndo); + + const QIcon redoIcon = QIcon::fromTheme("edit-redo", QIcon(rsrcPath + "/editredo.png")); + actionRedo = menu->addAction(redoIcon, tr("&Redo"), textEdit, &QTextEdit::redo); + actionRedo->setPriority(QAction::LowPriority); + actionRedo->setShortcut(QKeySequence::Redo); + tb->addAction(actionRedo); + menu->addSeparator(); + +#ifndef QT_NO_CLIPBOARD + const QIcon cutIcon = QIcon::fromTheme("edit-cut", QIcon(rsrcPath + "/editcut.png")); + actionCut = menu->addAction(cutIcon, tr("Cu&t"), textEdit, &QTextEdit::cut); + actionCut->setPriority(QAction::LowPriority); + actionCut->setShortcut(QKeySequence::Cut); + tb->addAction(actionCut); + + const QIcon copyIcon = QIcon::fromTheme("edit-copy", QIcon(rsrcPath + "/editcopy.png")); + actionCopy = menu->addAction(copyIcon, tr("&Copy"), textEdit, &QTextEdit::copy); + actionCopy->setPriority(QAction::LowPriority); + actionCopy->setShortcut(QKeySequence::Copy); + tb->addAction(actionCopy); + + const QIcon pasteIcon = QIcon::fromTheme("edit-paste", QIcon(rsrcPath + "/editpaste.png")); + actionPaste = menu->addAction(pasteIcon, tr("&Paste"), textEdit, &QTextEdit::paste); + actionPaste->setPriority(QAction::LowPriority); + actionPaste->setShortcut(QKeySequence::Paste); + tb->addAction(actionPaste); + if (const QMimeData *md = QApplication::clipboard()->mimeData()) + actionPaste->setEnabled(md->hasText()); +#endif +} + +void GameInformationDialog::setupTextActions() +{ + QToolBar *tb = addToolBar(tr("Format Actions")); + QMenu *menu = menuBar()->addMenu(tr("F&ormat")); + + const QIcon boldIcon = QIcon::fromTheme("format-text-bold", QIcon(rsrcPath + "/textbold.png")); + actionTextBold = menu->addAction(boldIcon, tr("&Bold"), this, &GameInformationDialog::textBold); + actionTextBold->setShortcut(QKeySequence::Bold); + actionTextBold->setPriority(QAction::LowPriority); + QFont bold; + bold.setBold(true); + actionTextBold->setFont(bold); + tb->addAction(actionTextBold); + actionTextBold->setCheckable(true); + + const QIcon italicIcon = QIcon::fromTheme("format-text-italic", QIcon(rsrcPath + "/textitalic.png")); + actionTextItalic = menu->addAction(italicIcon, tr("&Italic"), this, &GameInformationDialog::textItalic); + actionTextItalic->setPriority(QAction::LowPriority); + actionTextItalic->setShortcut(QKeySequence::Italic); + QFont italic; + italic.setItalic(true); + actionTextItalic->setFont(italic); + tb->addAction(actionTextItalic); + actionTextItalic->setCheckable(true); + + const QIcon underlineIcon = QIcon::fromTheme("format-text-underline", QIcon(rsrcPath + "/textunder.png")); + actionTextUnderline = menu->addAction(underlineIcon, tr("&Underline"), this, &GameInformationDialog::textUnderline); + actionTextUnderline->setShortcut(QKeySequence::Underline); + actionTextUnderline->setPriority(QAction::LowPriority); + QFont underline; + underline.setUnderline(true); + actionTextUnderline->setFont(underline); + tb->addAction(actionTextUnderline); + actionTextUnderline->setCheckable(true); + + menu->addSeparator(); + + const QIcon leftIcon = QIcon::fromTheme("format-justify-left", QIcon(rsrcPath + "/textleft.png")); + actionAlignLeft = new QAction(leftIcon, tr("&Left"), this); + actionAlignLeft->setShortcut(Qt::CTRL | Qt::Key_L); + actionAlignLeft->setCheckable(true); + actionAlignLeft->setPriority(QAction::LowPriority); + const QIcon centerIcon = QIcon::fromTheme("format-justify-center", QIcon(rsrcPath + "/textcenter.png")); + actionAlignCenter = new QAction(centerIcon, tr("C&enter"), this); + actionAlignCenter->setShortcut(Qt::CTRL | Qt::Key_E); + actionAlignCenter->setCheckable(true); + actionAlignCenter->setPriority(QAction::LowPriority); + const QIcon rightIcon = QIcon::fromTheme("format-justify-right", QIcon(rsrcPath + "/textright.png")); + actionAlignRight = new QAction(rightIcon, tr("&Right"), this); + actionAlignRight->setShortcut(Qt::CTRL | Qt::Key_R); + actionAlignRight->setCheckable(true); + actionAlignRight->setPriority(QAction::LowPriority); + const QIcon fillIcon = QIcon::fromTheme("format-justify-fill", QIcon(rsrcPath + "/textjustify.png")); + actionAlignJustify = new QAction(fillIcon, tr("&Justify"), this); + actionAlignJustify->setShortcut(Qt::CTRL | Qt::Key_J); + actionAlignJustify->setCheckable(true); + actionAlignJustify->setPriority(QAction::LowPriority); + const QIcon indentMoreIcon = QIcon::fromTheme("format-indent-more", QIcon(rsrcPath + "/format-indent-more.png")); + actionIndentMore = menu->addAction(indentMoreIcon, tr("&Indent"), this, &GameInformationDialog::indent); + actionIndentMore->setShortcut(Qt::CTRL | Qt::Key_BracketRight); + actionIndentMore->setPriority(QAction::LowPriority); + const QIcon indentLessIcon = QIcon::fromTheme("format-indent-less", QIcon(rsrcPath + "/format-indent-less.png")); + actionIndentLess = menu->addAction(indentLessIcon, tr("&Unindent"), this, &GameInformationDialog::unindent); + actionIndentLess->setShortcut(Qt::CTRL | Qt::Key_BracketLeft); + actionIndentLess->setPriority(QAction::LowPriority); + + // Make sure the alignLeft is always left of the alignRight + QActionGroup *alignGroup = new QActionGroup(this); + connect(alignGroup, &QActionGroup::triggered, this, &GameInformationDialog::textAlign); + + if (QApplication::isLeftToRight()) { + alignGroup->addAction(actionAlignLeft); + alignGroup->addAction(actionAlignCenter); + alignGroup->addAction(actionAlignRight); + } else { + alignGroup->addAction(actionAlignRight); + alignGroup->addAction(actionAlignCenter); + alignGroup->addAction(actionAlignLeft); + } + alignGroup->addAction(actionAlignJustify); + + tb->addActions(alignGroup->actions()); + menu->addActions(alignGroup->actions()); + tb->addAction(actionIndentMore); + tb->addAction(actionIndentLess); + menu->addAction(actionIndentMore); + menu->addAction(actionIndentLess); + + menu->addSeparator(); + + QPixmap pix(16, 16); + pix.fill(Qt::black); + actionTextColor = menu->addAction(pix, tr("&Color..."), this, &GameInformationDialog::textColor); + tb->addAction(actionTextColor); + + menu->addSeparator(); + + const QIcon checkboxIcon = QIcon::fromTheme("status-checkbox-checked", QIcon(rsrcPath + "/checkbox-checked.png")); + actionToggleCheckState = menu->addAction(checkboxIcon, tr("Chec&ked"), this, &GameInformationDialog::setChecked); + actionToggleCheckState->setShortcut(Qt::CTRL | Qt::Key_K); + actionToggleCheckState->setCheckable(true); + actionToggleCheckState->setPriority(QAction::LowPriority); + tb->addAction(actionToggleCheckState); + + tb = addToolBar(tr("Format Actions")); + tb->setAllowedAreas(Qt::TopToolBarArea | Qt::BottomToolBarArea); + addToolBarBreak(Qt::TopToolBarArea); + addToolBar(tb); + + comboStyle = new QComboBox(tb); + tb->addWidget(comboStyle); + comboStyle->addItem("Standard"); + comboStyle->addItem("Bullet List (Disc)"); + comboStyle->addItem("Bullet List (Circle)"); + comboStyle->addItem("Bullet List (Square)"); + comboStyle->addItem("Task List (Unchecked)"); + comboStyle->addItem("Task List (Checked)"); + comboStyle->addItem("Ordered List (Decimal)"); + comboStyle->addItem("Ordered List (Alpha lower)"); + comboStyle->addItem("Ordered List (Alpha upper)"); + comboStyle->addItem("Ordered List (Roman lower)"); + comboStyle->addItem("Ordered List (Roman upper)"); + comboStyle->addItem("Heading 1"); + comboStyle->addItem("Heading 2"); + comboStyle->addItem("Heading 3"); + comboStyle->addItem("Heading 4"); + comboStyle->addItem("Heading 5"); + comboStyle->addItem("Heading 6"); + + connect(comboStyle, QOverload::of(&QComboBox::activated), this, &GameInformationDialog::textStyle); + + comboFont = new QFontComboBox(tb); + tb->addWidget(comboFont); + connect(comboFont, &QComboBox::textActivated, this, &GameInformationDialog::textFamily); + + comboSize = new QComboBox(tb); + comboSize->setObjectName("comboSize"); + tb->addWidget(comboSize); + comboSize->setEditable(true); + + const QList standardSizes = QFontDatabase::standardSizes(); + for (int size : standardSizes) + comboSize->addItem(QString::number(size)); + comboSize->setCurrentIndex(standardSizes.indexOf(QApplication::font().pointSize())); + + connect(comboSize, &QComboBox::textActivated, this, &GameInformationDialog::textSize); +} + +bool GameInformationDialog::load(const QString &f) +{ + if (!QFile::exists(f)) + return false; + QFile file(f); + if (!file.open(QFile::ReadOnly)) + return false; + + QByteArray data = file.readAll(); +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) + QTextCodec *codec = Qt::codecForHtml(data); + QString str = codec->toUnicode(data); +#else + QString str = QString::fromUtf8(data); +#endif + if (Qt::mightBeRichText(str)) { + QUrl baseUrl = (f.front() == QLatin1Char(':') ? QUrl(f) : QUrl::fromLocalFile(f)).adjusted(QUrl::RemoveFilename); + textEdit->document()->setBaseUrl(baseUrl); + textEdit->setHtml(str); + } else { +#if QT_CONFIG(textmarkdownreader) + QMimeDatabase db; + if (db.mimeTypeForFileNameAndData(f, data).name() == QLatin1String("text/markdown")) + textEdit->setMarkdown(QString::fromUtf8(data)); + else +#endif + textEdit->setPlainText(QString::fromUtf8(data)); + } + + setCurrentFileName(f); + return true; +} + +bool GameInformationDialog::result() const +{ + return m_result; +} + +void GameInformationDialog::setResult(bool result) +{ + m_result = result; +} + +void GameInformationDialog::open() +{ + Qt::WindowModality modality = windowModality(); + if (modality != Qt::WindowModal) { + m_resetModalityTo = modality; + m_wasModalitySet = testAttribute(Qt::WA_SetWindowModality); + setWindowModality(Qt::WindowModal); + setAttribute(Qt::WA_SetWindowModality, false); +#ifdef Q_OS_MAC + setParent(parentWidget(), Qt::Sheet); +#endif + } + setResult(false); + show(); +} + +bool GameInformationDialog::exec() +{ + bool deleteOnClose = testAttribute(Qt::WA_DeleteOnClose); + setAttribute(Qt::WA_DeleteOnClose, false); + + resetModalitySetByOpen(); + + bool wasShowModal = testAttribute(Qt::WA_ShowModal); + setAttribute(Qt::WA_ShowModal, true); + setResult(false); + + show(); + + QPointer guard = this; + + eventLoop.exec(QEventLoop::DialogExec); + + if (guard.isNull()) + return false; + + setAttribute(Qt::WA_ShowModal, wasShowModal); + + bool res = result(); + + if (deleteOnClose) + delete this; + + return m_result; +} + +void GameInformationDialog::done(bool result) +{ + hide(result); + finalize(result, result); +} + +void GameInformationDialog::accept() +{ + done(true); +} + +void GameInformationDialog::reject() +{ + if (textEdit->document()->isModified()) + { + const QMessageBox::StandardButton ret = + QMessageBox::warning(this, QCoreApplication::applicationName(), + tr("The document has been modified.\n" + "Do you want to save your changes?"), + QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel); + if (ret == QMessageBox::Save) + { + accept(); + return; + } + else if (ret == QMessageBox::Discard) + { + // continue + } + else + { + return; + } + } + + done(false); +} + +bool GameInformationDialog::maybeSave() +{ + if (!textEdit->document()->isModified()) + return true; + + const QMessageBox::StandardButton ret = + QMessageBox::warning(this, QCoreApplication::applicationName(), + tr("The document has been modified.\n" + "Do you want to save your changes?"), + QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel); + if (ret == QMessageBox::Save) + return fileSave(); + else if (ret == QMessageBox::Cancel) + return false; + return true; +} + +void GameInformationDialog::setCurrentFileName(const QString &fileName) +{ + this->fileName = fileName; + textEdit->document()->setModified(false); + + QString shownName; + if (fileName.isEmpty()) + shownName = "untitled.txt"; + else + shownName = QFileInfo(fileName).fileName(); + + setWindowTitle(tr("%1[*] - %2").arg(shownName, QCoreApplication::applicationName())); + setWindowModified(false); +} + +void GameInformationDialog::fileNew() +{ + if (maybeSave()) { + textEdit->clear(); + setCurrentFileName(QString()); + } +} + +void GameInformationDialog::fileOpen() +{ + QFileDialog fileDialog(this, tr("Open File...")); + fileDialog.setAcceptMode(QFileDialog::AcceptOpen); + fileDialog.setFileMode(QFileDialog::ExistingFile); + fileDialog.setMimeTypeFilters(QStringList() +#if QT_CONFIG(texthtmlparser) + << "text/html" +#endif +#if QT_CONFIG(textmarkdownreader) + + << "text/markdown" +#endif + << "text/plain"); + if (fileDialog.exec() != QDialog::Accepted) + return; + const QString fn = fileDialog.selectedFiles().first(); + if (load(fn)) + statusBar()->showMessage(tr("Opened \"%1\"").arg(QDir::toNativeSeparators(fn))); + else + statusBar()->showMessage(tr("Could not open \"%1\"").arg(QDir::toNativeSeparators(fn))); +} + +bool GameInformationDialog::fileSave() +{ + if (fileName.isEmpty()) + return fileSaveAs(); + if (fileName.startsWith(QStringLiteral(":/"))) + return fileSaveAs(); + + QTextDocumentWriter writer(fileName); + bool success = writer.write(textEdit->document()); + if (success) { + textEdit->document()->setModified(false); + statusBar()->showMessage(tr("Wrote \"%1\"").arg(QDir::toNativeSeparators(fileName))); + } else { + statusBar()->showMessage(tr("Could not write to file \"%1\"") + .arg(QDir::toNativeSeparators(fileName))); + } + return success; +} + +bool GameInformationDialog::fileSaveAs() +{ + QFileDialog fileDialog(this, tr("Save as...")); + fileDialog.setAcceptMode(QFileDialog::AcceptSave); + QStringList mimeTypes; + mimeTypes << "text/plain" +#if QT_CONFIG(textodfwriter) + << "application/vnd.oasis.opendocument.text" +#endif +#if QT_CONFIG(textmarkdownwriter) + << "text/markdown" +#endif + << "text/html"; + fileDialog.setMimeTypeFilters(mimeTypes); +#if QT_CONFIG(textodfwriter) + fileDialog.setDefaultSuffix("odt"); +#endif + if (fileDialog.exec() != QDialog::Accepted) + return false; + const QString fn = fileDialog.selectedFiles().first(); + setCurrentFileName(fn); + return fileSave(); +} + +void GameInformationDialog::filePrint() +{ +#if defined(QT_PRINTSUPPORT_LIB) && QT_CONFIG(printdialog) + QPrinter printer(QPrinter::HighResolution); + QPrintDialog *dlg = new QPrintDialog(&printer, this); +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) + if (textEdit->textCursor().hasSelection()) + dlg->addEnabledOption(QAbstractPrintDialog::PrintSelection); +#endif + dlg->setWindowTitle(tr("Print Document")); + if (dlg->exec() == QDialog::Accepted) + textEdit->print(&printer); + delete dlg; +#endif +} + +void GameInformationDialog::filePrintPreview() +{ +#if defined(QT_PRINTSUPPORT_LIB) && QT_CONFIG(printpreviewdialog) + QPrinter printer(QPrinter::HighResolution); + QPrintPreviewDialog preview(&printer, this); + connect(&preview, &QPrintPreviewDialog::paintRequested, this, &GameInformationDialog::printPreview); + preview.exec(); +#endif +} + +void GameInformationDialog::printPreview(QPrinter *printer) +{ +#if defined(QT_PRINTSUPPORT_LIB) && QT_CONFIG(printer) + textEdit->print(printer); +#else + Q_UNUSED(printer) +#endif +} + + +void GameInformationDialog::filePrintPdf() +{ +#if defined(QT_PRINTSUPPORT_LIB) && QT_CONFIG(printer) +//! [0] + QFileDialog fileDialog(this, tr("Export PDF")); + fileDialog.setAcceptMode(QFileDialog::AcceptSave); + fileDialog.setMimeTypeFilters(QStringList("application/pdf")); + fileDialog.setDefaultSuffix("pdf"); + if (fileDialog.exec() != QDialog::Accepted) + return; + QString fileName = fileDialog.selectedFiles().first(); + QPrinter printer(QPrinter::HighResolution); + printer.setOutputFormat(QPrinter::PdfFormat); + printer.setOutputFileName(fileName); + textEdit->document()->print(&printer); + statusBar()->showMessage(tr("Exported \"%1\"") + .arg(QDir::toNativeSeparators(fileName))); +//! [0] +#endif +} + +void GameInformationDialog::textBold() +{ + QTextCharFormat fmt; + fmt.setFontWeight(actionTextBold->isChecked() ? QFont::Bold : QFont::Normal); + mergeFormatOnWordOrSelection(fmt); +} + +void GameInformationDialog::textUnderline() +{ + QTextCharFormat fmt; + fmt.setFontUnderline(actionTextUnderline->isChecked()); + mergeFormatOnWordOrSelection(fmt); +} + +void GameInformationDialog::textItalic() +{ + QTextCharFormat fmt; + fmt.setFontItalic(actionTextItalic->isChecked()); + mergeFormatOnWordOrSelection(fmt); +} + +void GameInformationDialog::textFamily(const QString &f) +{ + QTextCharFormat fmt; + fmt.setFontFamily(f); + mergeFormatOnWordOrSelection(fmt); +} + +void GameInformationDialog::textSize(const QString &p) +{ + qreal pointSize = p.toFloat(); + if (p.toFloat() > 0) { + QTextCharFormat fmt; + fmt.setFontPointSize(pointSize); + mergeFormatOnWordOrSelection(fmt); + } +} + +void GameInformationDialog::textStyle(int styleIndex) +{ + QTextCursor cursor = textEdit->textCursor(); + QTextListFormat::Style style = QTextListFormat::ListStyleUndefined; + QTextBlockFormat::MarkerType marker = QTextBlockFormat::MarkerType::NoMarker; + + switch (styleIndex) { + case 1: + style = QTextListFormat::ListDisc; + break; + case 2: + style = QTextListFormat::ListCircle; + break; + case 3: + style = QTextListFormat::ListSquare; + break; + case 4: + if (cursor.currentList()) + style = cursor.currentList()->format().style(); + else + style = QTextListFormat::ListDisc; + marker = QTextBlockFormat::MarkerType::Unchecked; + break; + case 5: + if (cursor.currentList()) + style = cursor.currentList()->format().style(); + else + style = QTextListFormat::ListDisc; + marker = QTextBlockFormat::MarkerType::Checked; + break; + case 6: + style = QTextListFormat::ListDecimal; + break; + case 7: + style = QTextListFormat::ListLowerAlpha; + break; + case 8: + style = QTextListFormat::ListUpperAlpha; + break; + case 9: + style = QTextListFormat::ListLowerRoman; + break; + case 10: + style = QTextListFormat::ListUpperRoman; + break; + default: + break; + } + + cursor.beginEditBlock(); + + QTextBlockFormat blockFmt = cursor.blockFormat(); + + if (style == QTextListFormat::ListStyleUndefined) { + blockFmt.setObjectIndex(-1); + int headingLevel = styleIndex >= 11 ? styleIndex - 11 + 1 : 0; // H1 to H6, or Standard + blockFmt.setHeadingLevel(headingLevel); + cursor.setBlockFormat(blockFmt); + + int sizeAdjustment = headingLevel ? 4 - headingLevel : 0; // H1 to H6: +3 to -2 + QTextCharFormat fmt; + fmt.setFontWeight(headingLevel ? QFont::Bold : QFont::Normal); + fmt.setProperty(QTextFormat::FontSizeAdjustment, sizeAdjustment); + cursor.select(QTextCursor::LineUnderCursor); + cursor.mergeCharFormat(fmt); + textEdit->mergeCurrentCharFormat(fmt); + } else { + blockFmt.setMarker(marker); + cursor.setBlockFormat(blockFmt); + QTextListFormat listFmt; + if (cursor.currentList()) { + listFmt = cursor.currentList()->format(); + } else { + listFmt.setIndent(blockFmt.indent() + 1); + blockFmt.setIndent(0); + cursor.setBlockFormat(blockFmt); + } + listFmt.setStyle(style); + cursor.createList(listFmt); + } + + cursor.endEditBlock(); +} + +void GameInformationDialog::textColor() +{ + QColor col = QColorDialog::getColor(textEdit->textColor(), this); + if (!col.isValid()) + return; + QTextCharFormat fmt; + fmt.setForeground(col); + mergeFormatOnWordOrSelection(fmt); + colorChanged(col); +} + +void GameInformationDialog::textAlign(QAction *a) +{ + if (a == actionAlignLeft) + textEdit->setAlignment(Qt::AlignLeft | Qt::AlignAbsolute); + else if (a == actionAlignCenter) + textEdit->setAlignment(Qt::AlignHCenter); + else if (a == actionAlignRight) + textEdit->setAlignment(Qt::AlignRight | Qt::AlignAbsolute); + else if (a == actionAlignJustify) + textEdit->setAlignment(Qt::AlignJustify); +} + +void GameInformationDialog::setChecked(bool checked) +{ + textStyle(checked ? 5 : 4); +} + +void GameInformationDialog::indent() +{ + modifyIndentation(1); +} + +void GameInformationDialog::unindent() +{ + modifyIndentation(-1); +} + +void GameInformationDialog::modifyIndentation(int amount) +{ + QTextCursor cursor = textEdit->textCursor(); + cursor.beginEditBlock(); + if (cursor.currentList()) { + QTextListFormat listFmt = cursor.currentList()->format(); + // See whether the line above is the list we want to move this item into, + // or whether we need a new list. + QTextCursor above(cursor); + above.movePosition(QTextCursor::Up); + if (above.currentList() && listFmt.indent() + amount == above.currentList()->format().indent()) { + above.currentList()->add(cursor.block()); + } else { + listFmt.setIndent(listFmt.indent() + amount); + cursor.createList(listFmt); + } + } else { + QTextBlockFormat blockFmt = cursor.blockFormat(); + blockFmt.setIndent(blockFmt.indent() + amount); + cursor.setBlockFormat(blockFmt); + } + cursor.endEditBlock(); +} + +void GameInformationDialog::currentCharFormatChanged(const QTextCharFormat &format) +{ + fontChanged(format.font()); + colorChanged(format.foreground().color()); +} + +void GameInformationDialog::cursorPositionChanged() +{ + alignmentChanged(textEdit->alignment()); + QTextList *list = textEdit->textCursor().currentList(); + if (list) { + switch (list->format().style()) { + case QTextListFormat::ListDisc: + comboStyle->setCurrentIndex(1); + break; + case QTextListFormat::ListCircle: + comboStyle->setCurrentIndex(2); + break; + case QTextListFormat::ListSquare: + comboStyle->setCurrentIndex(3); + break; + case QTextListFormat::ListDecimal: + comboStyle->setCurrentIndex(6); + break; + case QTextListFormat::ListLowerAlpha: + comboStyle->setCurrentIndex(7); + break; + case QTextListFormat::ListUpperAlpha: + comboStyle->setCurrentIndex(8); + break; + case QTextListFormat::ListLowerRoman: + comboStyle->setCurrentIndex(9); + break; + case QTextListFormat::ListUpperRoman: + comboStyle->setCurrentIndex(10); + break; + default: + comboStyle->setCurrentIndex(-1); + break; + } + switch (textEdit->textCursor().block().blockFormat().marker()) { + case QTextBlockFormat::MarkerType::NoMarker: + actionToggleCheckState->setChecked(false); + break; + case QTextBlockFormat::MarkerType::Unchecked: + comboStyle->setCurrentIndex(4); + actionToggleCheckState->setChecked(false); + break; + case QTextBlockFormat::MarkerType::Checked: + comboStyle->setCurrentIndex(5); + actionToggleCheckState->setChecked(true); + break; + } + } else { + int headingLevel = textEdit->textCursor().blockFormat().headingLevel(); + comboStyle->setCurrentIndex(headingLevel ? headingLevel + 10 : 0); + } +} + +void GameInformationDialog::clipboardDataChanged() +{ +#ifndef QT_NO_CLIPBOARD + if (const QMimeData *md = QApplication::clipboard()->mimeData()) + actionPaste->setEnabled(md->hasText()); +#endif +} + +void GameInformationDialog::about() +{ + QMessageBox::about(this, tr("About"), tr("This example demonstrates Qt's " + "rich text editing facilities in action, providing an example " + "document for you to experiment with.")); +} + +void GameInformationDialog::mergeFormatOnWordOrSelection(const QTextCharFormat &format) +{ + QTextCursor cursor = textEdit->textCursor(); + if (!cursor.hasSelection()) + cursor.select(QTextCursor::WordUnderCursor); + cursor.mergeCharFormat(format); + textEdit->mergeCurrentCharFormat(format); +} + +void GameInformationDialog::fontChanged(const QFont &f) +{ + comboFont->setCurrentIndex(comboFont->findText(QFontInfo(f).family())); + comboSize->setCurrentIndex(comboSize->findText(QString::number(f.pointSize()))); + actionTextBold->setChecked(f.bold()); + actionTextItalic->setChecked(f.italic()); + actionTextUnderline->setChecked(f.underline()); +} + +void GameInformationDialog::colorChanged(const QColor &c) +{ + QPixmap pix(16, 16); + pix.fill(c); + actionTextColor->setIcon(pix); +} + +void GameInformationDialog::alignmentChanged(Qt::Alignment a) +{ + if (a & Qt::AlignLeft) + actionAlignLeft->setChecked(true); + else if (a & Qt::AlignHCenter) + actionAlignCenter->setChecked(true); + else if (a & Qt::AlignRight) + actionAlignRight->setChecked(true); + else if (a & Qt::AlignJustify) + actionAlignJustify->setChecked(true); +} + +void GameInformationDialog::resetModalitySetByOpen() +{ + if (m_resetModalityTo != -1 && !testAttribute(Qt::WA_SetWindowModality)) { + // open() changed the window modality and the user didn't touch it afterwards; restore it + setWindowModality(Qt::WindowModality(m_resetModalityTo)); + setAttribute(Qt::WA_SetWindowModality, m_wasModalitySet); +#ifdef Q_OS_MAC + Q_ASSERT(m_resetModalityTo != Qt::WindowModal); + setParent(parentWidget(), Qt::Dialog); +#endif + } + m_resetModalityTo = -1; +} + +void GameInformationDialog::finalize(bool resultCode, bool dialogCode) +{ + if (dialogCode) + emit accepted(); + else + emit rejected(); + emit finished(resultCode); +} + +void GameInformationDialog::hide(bool result) +{ + setResult(result); + QMainWindow::hide(); + + // close_helper(QWidgetPrivate::CloseNoEvent); + resetModalitySetByOpen(); +} diff --git a/src/editor/dialogs/gameinformationdialog.h b/src/editor/dialogs/gameinformationdialog.h index 9ef9ed8..1953a30 100644 --- a/src/editor/dialogs/gameinformationdialog.h +++ b/src/editor/dialogs/gameinformationdialog.h @@ -1,19 +1,125 @@ #pragma once -#include +#include +#include +#include +#include -#include +QT_BEGIN_NAMESPACE +class QAction; +class QComboBox; +class QFontComboBox; +class QTextEdit; +class QTextCharFormat; +class QMenu; +class QPrinter; +QT_END_NAMESPACE -namespace Ui { class GameInformationDialog; } - -class GameInformationDialog : public QDialog +class GameInformationDialog : public QMainWindow { Q_OBJECT public: - explicit GameInformationDialog(QWidget *parent = nullptr); - ~GameInformationDialog(); + GameInformationDialog(QWidget *parent = nullptr); + + bool load(const QString &f); + + bool result() const; + + void setResult(bool result); + +signals: + void finished(int result); + void accepted(); + void rejected(); + +public slots: + virtual void open(); + virtual bool exec(); + virtual void done(bool result); + virtual void accept(); + virtual void reject(); + + void fileNew(); + +protected: + void closeEvent(QCloseEvent *e) override; + +private slots: + void fileOpen(); + bool fileSave(); + bool fileSaveAs(); + void filePrint(); + void filePrintPreview(); + void filePrintPdf(); + + void textBold(); + void textUnderline(); + void textItalic(); + void textFamily(const QString &f); + void textSize(const QString &p); + void textStyle(int styleIndex); + void textColor(); + void textAlign(QAction *a); + void setChecked(bool checked); + void indent(); + void unindent(); + + void currentCharFormatChanged(const QTextCharFormat &format); + void cursorPositionChanged(); + + void clipboardDataChanged(); + void about(); + void printPreview(QPrinter *); private: - const std::unique_ptr m_ui; + void setupFileActions(); + void setupEditActions(); + void setupTextActions(); + bool maybeSave(); + void setCurrentFileName(const QString &fileName); + void modifyIndentation(int amount); + + void mergeFormatOnWordOrSelection(const QTextCharFormat &format); + void fontChanged(const QFont &f); + void colorChanged(const QColor &c); + void alignmentChanged(Qt::Alignment a); + + void resetModalitySetByOpen(); + void finalize(bool resultCode, bool dialogCode); + void hide(bool result); + + QAction *actionSave; + QAction *actionTextBold; + QAction *actionTextUnderline; + QAction *actionTextItalic; + QAction *actionTextColor; + QAction *actionAlignLeft; + QAction *actionAlignCenter; + QAction *actionAlignRight; + QAction *actionAlignJustify; + QAction *actionIndentLess; + QAction *actionIndentMore; + QAction *actionToggleCheckState; + QAction *actionUndo; + QAction *actionRedo; +#ifndef QT_NO_CLIPBOARD + QAction *actionCut; + QAction *actionCopy; + QAction *actionPaste; +#endif + + QComboBox *comboStyle; + QFontComboBox *comboFont; + QComboBox *comboSize; + + QToolBar *tb; + QString fileName; + QTextEdit *textEdit; + + QEventLoop eventLoop; + + bool m_result{}; + int m_resetModalityTo; + bool m_wasModalitySet; }; diff --git a/src/editor/dialogs/gameinformationdialog.ui b/src/editor/dialogs/gameinformationdialog.ui index bc57fb2..d20dc6e 100644 --- a/src/editor/dialogs/gameinformationdialog.ui +++ b/src/editor/dialogs/gameinformationdialog.ui @@ -13,7 +13,19 @@ Game Information - + + + 0 + + + 0 + + + 0 + + + 0 + @@ -66,13 +78,16 @@ - - - - - + + + + + + + + diff --git a/src/editor/mainwindow.cpp b/src/editor/mainwindow.cpp index ea68149..9cb88f6 100644 --- a/src/editor/mainwindow.cpp +++ b/src/editor/mainwindow.cpp @@ -742,7 +742,7 @@ template void MainWindow::createFor(); void MainWindow::showGameInformation() { GameInformationDialog dialog{this}; - if (dialog.exec() == QDialog::Accepted) + if (dialog.exec() /*== QDialog::Accepted*/) changed(); } diff --git a/src/editor/models/actionscontainermodel.cpp b/src/editor/models/actionscontainermodel.cpp index 3d8917c..384bfad 100644 --- a/src/editor/models/actionscontainermodel.cpp +++ b/src/editor/models/actionscontainermodel.cpp @@ -144,6 +144,7 @@ QMimeData *ActionsContainerModel::mimeData(const QModelIndexList &indexes) const bool ActionsContainerModel::canDropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) const { + qDebug() << data->formats(); // if the drop is on an item, reject drop if (parent.isValid() && row == -1 && column == -1) { @@ -156,6 +157,7 @@ bool ActionsContainerModel::canDropMimeData(const QMimeData *data, Qt::DropActio bool ActionsContainerModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) { + qDebug() << data->formats(); // check if the action is supported if (!data || !(action == Qt::CopyAction || action == Qt::MoveAction)) { diff --git a/src/editor/widgets/objectselectorwidget.ui b/src/editor/widgets/objectselectorwidget.ui index a9d5411..ad6c1e0 100644 --- a/src/editor/widgets/objectselectorwidget.ui +++ b/src/editor/widgets/objectselectorwidget.ui @@ -33,7 +33,11 @@ 0 - + + + true + +