👉아래는 QT6버전 프로그램 소스 코드 입니다.
Below is the QT6 version program source code.
👉약간의 기능이 더 추가 되어 있습니다.
There are a few more features added.
1.main.cpp
#include "mainwindow.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}
/*
* fontAwesome : 폰트다운로드 / fontdownload
* https://fontawesome.com/search?q=pdf&o=r
*
*
*
*/
2.mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QTextCharFormat> // QTextCharFormat 사용을 위해 포함 / Included for use with QTextCharFormat
#include <QFileInfo> // QFileInfo 사용을 위해 포함 / Included for using QFileInfo
// 드래그 이미지 / drag image
#include <QPoint> // 마우스 드래그 시작 위치 저장을 위해 추가 / Added to save the mouse drag start position
QT_BEGIN_NAMESPACE
namespace Ui {
class MainWindow;
}
QT_END_NAMESPACE
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
// QObject의 이벤트 필터 함수를 오버라이드합니다. / Override QObject's event filter function.
bool eventFilter(QObject *obj, QEvent *event) override;
private slots:
// == 파일 관련 슬롯 / File-related slots ==
bool saveFile(const QString &path);
void save(); // Save
void saveAs(); // Save As
void actionNew(); // 새 파일 / new file
void actionOpen(); // 파일 열기 / open
void actionExit(); // 종료 / Exit
// == 편집 / Edit ==
void actionFind(); // 찾기 기능 / find
void actionReplace(); // 바꾸기 기능 / replace
// == 뷰 ==
void actionZoomIn(); // 줌인 / zoom in
void actionZoomOut(); // 줌아웃 / zoom out
void actionZoomReset(); // 줌 초기화 / zoom reset
// == 이미지 / Image ==
void actionInsertImage(); // 이미지 삽입 / insert image
// == Export ==
void actionExportBase64(); // Export As Base64
void actionExportBase64WithImages(); // Export As Base64 for image
void actionExportPdf(); //Export As Pdf
// == Import ==
void actionImportBase64(); // Import As Base64
// == 텍스트 기능 / Text function ==
void actionBold(bool checked);
void actionItalic(bool checked);
void actionUnderline(bool checked);
void actionStrike(bool checked);
// -- 자동연결설정(이전에 사용하던 방식으로 경고 발생) --
// -- Automatic connection setup (warning occurs in the previous method) --
// void on_actionColor_triggered();
// void on_actionFont_triggered();
// -- 수동연결 --
void actionColor(); // 색상 선택은 토글이 아니므로 bool 인자가 없음 / Color selection is not a toggle, so there is no bool argument.
void actionFont(); // 폰트 선택은 토글이 아니므로 bool 인자가 없음 / Font selection is not a toggle, so there is no bool argument.
// 현재 커서 위치의 서식 변경을 감지하는 슬롯
// Slot to detect format changes at the current cursor position
void updateFormat(const QTextCharFormat &format);
// == 도움말 기능 / help ==
void actionHelp(); // 도움말 / help
void actionAbout(); // 정보 / about
private:
Ui::MainWindow *ui;
QString currentFilePath; // 현재 파일을 추적하는 변수 추가 / Add a variable to track the current file
// == 이미지 리사이즈를 위한 멤버 변수 / Member variables for image resizing ==
bool m_isResizing = false;
QPoint m_dragStartPos;
int m_originalImageWidth = 0;
int m_originalImageHeight = 0;
// == 줌인/줌아웃 설정을 위한 멤버 변수 / Member variables for zoom in/zoom out settings ==
// 폰트 크기 초기화용 변수 추가(변화하는 현재상태 저장)
// Add a variable to initialize the font size (save the current state as it changes)
int m_currentFontSize = 10;
// 기본 폰트 크기를 저장하는 멤버 변수(리셋할때의 값)
// Member variable that stores the default font size (value when reset)
int m_defaultPointSize = 10; // 초기 기본값 설정 / Set factory defaults
};
#endif // MAINWINDOW_H
3.mainwindow.cpp
#include "mainwindow.h"
#include "./ui_mainwindow.h"
// 코드 추가 / Add code
#include <QFileDialog>
#include <QFile>
#include <QTextStream>
#include <QMessageBox>
#include <QTextCharFormat>
#include <QColorDialog> // 색상 선택 다이얼로그 / Color selection dialog
#include <QFontDialog> // 폰트 선택 다이얼로그 / Font selection dialog
#include <QDir> // QDir 사용을 위해 추가 / Added for QDir use
#include <QFileInfo> // QFileInfo 사용을 위해 추가 / Added for using QFileInfo
#include <QUrl> // 이미지 파일 경로를 URL로 변환하기 위해 필요 / Needed to convert image file path to URL
#include <QImage> // 이미지 로드를 위해 필요 / Required for image loading
#include <QTextDocument> // QTextDocument::addResource 사용을 위해 필요 / Required for using QTextDocument::addResource
#include <QTextImageFormat> // 이미지 삽입 포맷 지정을 위해 필요 / Required to specify image insertion format
#include <QTextCursor>
#include <QInputDialog> // 사용자 입력 다이얼로그를 위해 추가 / Added for user input dialog
#include <QMouseEvent> // 마우스 이벤트 처리를 위해 추가 / Added for mouse event handling
#include <QtMath> // qMax, qMin 등을 위해 필요 (여기서는 qMax 사용) / Required for qMax, qMin, etc. (qMax is used here)
// export as base64
#include <QByteArray>
#include <QClipboard>
#include <QApplication>
// export base64 with images
#include <QRegularExpression>
#include <QRegularExpressionMatchIterator>
#include <QBuffer>
// pdf
#include <QPrinter>
#include <QFileDialog>
#include <QMessageBox>
#include <QPdfWriter>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
// 메인 윈도우의 중앙 영역에 textEdit 위젯을 배치
// Place the textEdit widget in the center area of the main window
setCentralWidget(ui->textEdit);
// == 파일 기능 / File functon ==
connect(ui->actionSave, &QAction::triggered, this, &MainWindow::save); // save
connect(ui->actionSave_As, &QAction::triggered, this, &MainWindow::saveAs); // saveas
connect(ui->actionExit, &QAction::triggered, this, &MainWindow::actionExit); // exit
connect(ui->actionNew, &QAction::triggered, this, &MainWindow::actionNew); // new
connect(ui->actionOpen, &QAction::triggered, this, &MainWindow::actionOpen);// open
connect(ui->actionExport_As_Base64, &QAction::triggered, this, &MainWindow::actionExportBase64); //save as base64
connect(ui->actionImport_Base64, &QAction::triggered, this, &MainWindow::actionImportBase64); //Import base64
connect(ui->actionExport_Base64_With_Images, &QAction::triggered, this, &MainWindow::actionExportBase64WithImages); //Import base64
connect(ui->actionExport_As_Pdf, &QAction::triggered, this, &MainWindow::actionExportPdf); //Export As Pdf
// == 편집 기능 구현 / Implementing editing functions ==
connect(ui->actionCopy, &QAction::triggered, ui->textEdit, &QTextEdit::copy); //copy
connect(ui->actionCut, &QAction::triggered, ui->textEdit, &QTextEdit::cut); // cut
connect(ui->actionPaste, &QAction::triggered, ui->textEdit, &QTextEdit::paste); // past
connect(ui->actionSelect_All, &QAction::triggered, ui->textEdit, &QTextEdit::selectAll); // select all
connect(ui->actionUndo, &QAction::triggered, ui->textEdit, &QTextEdit::undo); // undo
connect(ui->actionRedo, &QAction::triggered, ui->textEdit, &QTextEdit::redo); // redo
// == 뷰 / View ==
connect(ui->actionZoom_in, &QAction::triggered, this, &MainWindow::actionZoomIn);
connect(ui->actionZoom_out, &QAction::triggered, this, &MainWindow::actionZoomOut);
connect(ui->actionZoom_reset, &QAction::triggered, this, &MainWindow::actionZoomReset);
// 사용자 정의 슬롯 / custom slots
connect(ui->actionFind, &QAction::triggered, this, &MainWindow::actionFind); // find
connect(ui->actionReplace, &QAction::triggered, this, &MainWindow::actionReplace); // replace
// == 텍스트 / Text ==
// 볼드체 / Bold
// -- actionBold를 굵게/얇게 상태를 유지하는 '토글' 버튼으로 설정
// -- Set actionBold to a 'toggle' button that maintains the bold/thin state.
ui->actionBold->setCheckable(true);
// -- 툴바 액션 클릭 시 서식을 적용하도록 연결
// -- Connect to apply formatting when clicking a toolbar action
connect(ui->actionBold, &QAction::triggered, this, &MainWindow::actionBold);
// 이탤릭체 / Italic
ui->actionItalic->setCheckable(true);
connect(ui->actionItalic, &QAction::triggered, this, &MainWindow::actionItalic);
// 밑줄 / Underline
ui->actionUnderline->setCheckable(true);
connect(ui->actionUnderline, &QAction::triggered, this, &MainWindow::actionUnderline);
// 취소선 / strikethrough
ui->actionStrike->setCheckable(true);
connect(ui->actionStrike, &QAction::triggered, this, &MainWindow::actionStrike);
// 글꼴 색상 / font color
connect(ui->actionColor, &QAction::triggered, this, &MainWindow::actionColor);
// 글꼴 선택 / Select font
connect(ui->actionFont, &QAction::triggered, this, &MainWindow::actionFont);
// 이미지 이미지 드래그 리사이즈 / Image Image Drag Resize
ui->textEdit->viewport()->installEventFilter(this);
// == 이미지 삽입 / Insert image ==
connect(ui->actionInsert_image, &QAction::triggered, this, &MainWindow::actionInsertImage);
// -- 텍스트 커서의 서식이 바뀔 때마다 툴바 상태를 업데이트하도록 연결
// -- Connect to update the toolbar state whenever the text cursor's format changes.
connect(ui->textEdit, &QTextEdit::currentCharFormatChanged,
this, &MainWindow::updateFormat);
// == 도움말 기능 / Help ==
// Help
connect(ui->actionHelp, &QAction::triggered, this, &MainWindow::actionHelp);
// About
connect(ui->actionAbout, &QAction::triggered, this, &MainWindow::actionAbout);
}
// == 슬롯 / Slot ==
// Export As PDF
void MainWindow::actionExportPdf()
{
// 저장할 PDF 파일 경로 선택 / Select the path to save the PDF file
QString filePath = QFileDialog::getSaveFileName(this,
tr("PDF로 내보내기/Export pdf"),
QDir::homePath() + "/export.pdf",
tr("PDF 파일/PDF file (*.pdf)"));
if (filePath.isEmpty())
return;
// QPrinter 객체를 PDF 모드로 설정
// Set the QPrinter object to PDF mode
QPrinter printer(QPrinter::HighResolution);
printer.setOutputFormat(QPrinter::PdfFormat);
printer.setOutputFileName(filePath);
// 페이지 여백 및 용지 설정
// Page margins and paper settings
printer.setPageMargins(QMarginsF(15, 15, 15, 15));
printer.setPageSize(QPageSize(QPageSize::A4));
// QTextEdit의 문서를 프린터로 출력
ui->textEdit->document()->print(&printer);
QMessageBox::information(this, tr("완료/comolete"), tr("PDF로 성공적으로 내보냈습니다:\n%1").arg(filePath));
}
// Export base64 with images
void MainWindow::actionExportBase64WithImages()
{
QTextDocument *doc = ui->textEdit->document();
QString html = doc->toHtml();
// 이미지 경로를 Base64 데이터 URI로 변환
// Convert image path to Base64 data URI
static const QRegularExpression imgRegex(R"###(<img[^>]*src="([^"]+)"[^>]*>)###");
QRegularExpressionMatchIterator it = imgRegex.globalMatch(html);
while (it.hasNext()) {
QRegularExpressionMatch match = it.next();
QString imgTag = match.captured(0);
QString imgPath = match.captured(1);
if (imgPath.startsWith("data:image"))
continue;
QImage image(QUrl(imgPath).toLocalFile());
if (image.isNull())
continue;
QByteArray ba;
QBuffer buffer(&ba);
buffer.open(QIODevice::WriteOnly);
image.save(&buffer, "PNG");
QString base64Data = QString("data:image/png;base64,%1")
.arg(QString::fromLatin1(ba.toBase64()));
QString newTag = imgTag;
newTag.replace(imgPath, base64Data);
html.replace(imgTag, newTag);
}
// 최종 HTML을 Base64로 인코딩
// Encode the final HTML to Base64
QByteArray finalBase64 = html.toUtf8().toBase64();
// 파일 저장 대화 상자 / Save file dialog box
QString filePath = QFileDialog::getSaveFileName(this, tr("Export as Base64 HTML"),
QDir::homePath() + "/export_with_images.txt",
tr("텍스트 파일/text file (*.txt);;모든 파일 (*)"));
if (filePath.isEmpty())
return;
// 파일 저장 / Save file
QFile file(filePath);
if (file.open(QIODevice::WriteOnly | QIODevice::Text)) {
QTextStream out(&file);
out << finalBase64;
file.close();
QMessageBox::information(this, tr("완료/Complete"), tr("이미지를 포함한 Base64 HTML로 내보냈습니다."));
}
}
// base64 Import
void MainWindow::actionImportBase64()
{
// 파일 열기 대화 상자 / Open file dialog box
QString filePath = QFileDialog::getOpenFileName(
this, tr("Base64 파일 열기"),
QDir::homePath(),
tr("텍스트 파일 (*.txt);;모든 파일 (*)"));
if (filePath.isEmpty())
return;
// 파일 읽기 / read file
QFile file(filePath);
if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
return;
QByteArray base64Data = file.readAll();
// Base64 디코딩 / Base64 Encoding
QByteArray decoded = QByteArray::fromBase64(base64Data);
QString html = QString::fromUtf8(decoded);
// 문서를 완전히 새로 생성하여 기존 상태 리셋 / Reset the previous state by creating a completely new document
QTextDocument *newDoc = new QTextDocument(this);
ui->textEdit->setDocument(newDoc);
// HTML 로드 / HTML Load
ui->textEdit->setHtml(html);
QMessageBox::information(this, tr("완료/Complete"), tr("Base64 문서를 불러왔습니다."));
}
// base 64로 저장하기 / Save as base 64
void MainWindow::actionExportBase64()
{
// QTextEdit의 내용을 HTML로 가져옴 / Get the contents of QTextEdit as HTML
QString htmlContent = ui->textEdit->toHtml();
// HTML 문자열을 Base64로 인코딩 / Encode HTML string to Base64
QByteArray base64Data = htmlContent.toUtf8().toBase64();
// 사용자에게 저장 또는 클립보드 복사 선택지 제공 / Give users the option to save or copy to clipboard
QStringList options = {tr("파일로 저장"), tr("클립보드에 복사"), tr("취소")};
bool ok;
QString choice = QInputDialog::getItem(this, tr("Base64 내보내기"),
tr("작업 선택:"), options, 0, false, &ok);
if (!ok || choice == tr("취소"))
return;
if (choice == tr("파일로 저장")) {
QString filePath = QFileDialog::getSaveFileName(this,
tr("Base64 파일로 내보내기"),
QDir::homePath() + "/export_base64.txt",
tr("텍스트 파일 (*.txt);;모든 파일 (*)"));
if (!filePath.isEmpty()) {
QFile file(filePath);
if (file.open(QIODevice::WriteOnly | QIODevice::Text)) {
QTextStream out(&file);
out << base64Data;
file.close();
QMessageBox::information(this, tr("완료/Complete"), tr("Base64 데이터가 파일로 저장되었습니다."));
} else {
QMessageBox::critical(this, tr("오류/Error"), tr("파일을 저장할 수 없습니다:\n") + file.errorString());
}
}
} else if (choice == tr("클립보드에 복사/Copy to clipboard")) {
QClipboard *clipboard = QApplication::clipboard();
clipboard->setText(QString::fromUtf8(base64Data));
QMessageBox::information(this, tr("완료/Complete"), tr("Base64 데이터가 클립보드에 복사되었습니다."));
}
}
// HTML저장 / Save html
bool MainWindow::saveFile(const QString &path)
{
QFile file(path);
// QTextEdit의 내용을 가져옵니다.
//QString content = ui->textEdit->toPlainText(); // 일반 텍스트 저장 예시 / Plain text storage example
// 만약 리치 텍스트(HTML)로 저장하려면:
QString content = ui->textEdit->toHtml();
if (file.open(QIODevice::WriteOnly | QIODevice::Text)) {
QTextStream out(&file);
out << content;
file.close();
currentFilePath = path; // 저장 성공 시 경로 업데이트 / Update path when save is successful
setWindowTitle(QFileInfo(path).fileName() + " - Editor"); // 창 제목 변경 / Change window title
return true;
} else {
QMessageBox::critical(this, tr("오류/Error"), tr("파일을 저장할 수 없습니다:\n") + file.errorString());
return false;
}
}
// 다른 이름으로 저장하기 / Save As
void MainWindow::saveAs()
{
// QFileDialog를 사용하여 사용자에게 파일 경로를 묻습니다.
// Use QFileDialog to ask the user for a file path.
QString filePath = QFileDialog::getSaveFileName(this,
tr("문서 저장/Save Document"),
currentFilePath.isEmpty() ? QDir::homePath() : currentFilePath,
tr("텍스트 파일 (*.txt);;HTML 파일 (*.html);;모든 파일 (*)"));
if (!filePath.isEmpty()) {
saveFile(filePath);
}
}
// 저장하기 / Save
void MainWindow::save()
{
if (currentFilePath.isEmpty()) {
// 경로가 없으면 'Save As'를 호출합니다. / If there is no path, call 'Save As'.
saveAs();
} else {
// 경로가 있으면 기존 파일에 덮어씁니다. / If the path exists, it will overwrite the existing file.
saveFile(currentFilePath);
}
}
// 종료하기 슬롯 / Exit slot
void MainWindow::actionExit()
{
close(); // 메인 윈도우를 닫고 애플리케이션을 종료합니다. / Close the main window and exit the application.
}
// 새 파일 생성 슬롯 / New file creation slot
void MainWindow::actionNew()
{
ui->textEdit->clear();
currentFilePath.clear();
setWindowTitle(tr("Untitled - Editor"));
}
// 파일 열기 슬롯 / file open slot
void MainWindow::actionOpen()
{
QString filePath = QFileDialog::getOpenFileName(this,
tr("문서 열기/Open"),
QDir::homePath(),
tr("리치 텍스트 파일 (*.html *.htm);;텍스트 파일 (*.txt);;이미지 파일 (*.png *.jpg *.jpeg *.gif);;모든 파일 (*)"));
if (!filePath.isEmpty()) {
QFile file(filePath);
if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
QString content = file.readAll();
if (filePath.endsWith(".html", Qt::CaseInsensitive) || filePath.endsWith(".htm", Qt::CaseInsensitive)) {
ui->textEdit->setHtml(content); // HTML 형식으로 로드 / Load in HTML format
}
// 이미지 파일을 바로 열면 텍스트 편집기에 삽입 / Open the image file directly and insert it into a text editor
else if (filePath.endsWith(".png", Qt::CaseInsensitive) ||
filePath.endsWith(".jpg", Qt::CaseInsensitive) ||
filePath.endsWith(".jpeg", Qt::CaseInsensitive) ||
filePath.endsWith(".gif", Qt::CaseInsensitive)) {
ui->textEdit->clear();
QTextDocument *doc = ui->textEdit->document();
QUrl imageUrl = QUrl::fromLocalFile(filePath);
QImage image(filePath);
doc->addResource(QTextDocument::ImageResource, imageUrl, image);
QTextImageFormat imageFormat;
imageFormat.setWidth(image.width());
imageFormat.setHeight(image.height());
imageFormat.setName(imageUrl.toString());
QTextCursor cursor = ui->textEdit->textCursor();
cursor.insertImage(imageFormat);
} else {
ui->textEdit->setText(content); // 일반 텍스트로 로드 / Load as plain text
}
file.close();
currentFilePath = filePath;
setWindowTitle(QFileInfo(filePath).fileName() + tr(" - Editor"));
} else {
QMessageBox::critical(this, tr("오류/Error"), tr("파일을 열 수 없습니다:\n") + file.errorString());
}
}
}
// == 편집 기능 슬롯 / Edit function slot ==
// '찾기' 기능 / 'Find' function
void MainWindow::actionFind()
{
bool ok;
// 사용자에게 찾을 텍스트를 입력받습니다. / Ask the user to enter the text they want to find.
QString text = QInputDialog::getText(this, tr("텍스트 찾기"),
tr("찾을 단어를 입력하세요:"), QLineEdit::Normal,
"", &ok);
if (ok && !text.isEmpty()) {
QTextDocument::FindFlags flags = QTextDocument::FindFlags();
// QTextEdit::find()를 사용하여 다음 일치 항목을 찾고 선택합니다.
bool found = ui->textEdit->find(text, flags);
if (!found) {
QMessageBox::information(this, tr("찾기 결과"), tr("'%1'을(를) 찾을 수 없습니다.").arg(text));
}
}
}
// '바꾸기' 기능 / replace function
void MainWindow::actionReplace()
{
// 찾을 텍스트 입력 / Enter text to find
bool ok;
QString findText = QInputDialog::getText(this, tr("텍스트 바꾸기"),
tr("찾을 단어를 입력하세요:"), QLineEdit::Normal,
"", &ok);
if (!ok || findText.isEmpty()) return;
// 바꿀 텍스트 입력 / Enter text to find
QString replaceText = QInputDialog::getText(this, tr("텍스트 바꾸기"),
tr("바꿀 단어를 입력하세요:"), QLineEdit::Normal,
"", &ok);
if (!ok) return;
QTextCursor cursor = ui->textEdit->textCursor();
QTextDocument::FindFlags flags = QTextDocument::FindFlags();
int count = 0;
// 텍스트를 문서 시작부터 다시 찾기 시작하도록 커서를 문서 시작으로 이동
// Move the cursor to the beginning of the document to start searching text again from the beginning of the document
cursor.movePosition(QTextCursor::Start);
ui->textEdit->setTextCursor(cursor);
// 문서 끝까지 반복하며 찾기 및 바꾸기
// Find and replace until the end of the document
while (ui->textEdit->find(findText, flags)) {
// find()가 성공하면 텍스트는 이미 선택되어 있습니다.
// If find() succeeds, the text is already selected.
QTextCursor currentSelection = ui->textEdit->textCursor();
// 선택된 텍스트를 replaceText로 대체
// Replace the selected text with replaceText
currentSelection.insertText(replaceText);
count++;
// 다음 검색을 위해 커서를 삽입된 텍스트 뒤로 이동 (find가 자동으로 처리함)
// Move the cursor after the inserted text for the next search (find handles this automatically)
}
QMessageBox::information(this, tr("바꾸기 완료"), tr("총 %1개의 항목을 변경했습니다.").arg(count));
}
// == 뷰 기능 슬롯 / View function slot ==
//----------------------------------------------------------------
// -- 내장함수 기능을 사용하면 저장이나 내보내기 한 파일을 적용되지 않음--
// -- Using built-in functions does not apply to files saved or exported--
//----------------------------------------------------------------
// 줌인(내장함수) / Zoom in (built-in function)
// void MainWindow::actionZoomIn()
// {
// // ui->textEdit은 QMainWindow에 포함된 QTextEdit 위젯의 objectName이라고 가정합니다.
// if (ui->textEdit) {
// qDebug() << "Zoom In triggered";
// // 기본 줌 단계를 사용하여 확대 / Zoom in using the default zoom level
// ui->textEdit->zoomIn(1);
// }
// }
// 줌아웃(내장함수) / Zoom out (built-in function)
// void MainWindow::actionZoomOut()
// {
// if (ui->textEdit) {
// qDebug() << "Zoom Out triggered";
// // 기본 줌 단계를 사용하여 축소 / Zoom out using the default zoom level
// ui->textEdit->zoomOut(1);
// }
// }
//-----------------------------------------------------------------------
// 줌인 커스텀 / Zoom in custom
void MainWindow::actionZoomIn()
{
// ui->textEdit이 유효한지 확인합니다. / Check if ui->textEdit is valid.
if (!ui->textEdit) {
return;
}
// 현재 문서의 기본 폰트(또는 커서 위치의 폰트) 정보를 가져옵니다.
// Get information about the default font of the current document (or the font at the cursor position).
// 문서 전체의 기본 폰트 크기를 사용할지, 현재 커서의 폰트 크기를 사용할지 결정합니다.
// Determines whether to use the default font size for the entire document or the font size of the current cursor.
// 줌 기능은 보통 문서 전체의 기본 폰트 크기를 변경합니다.
// The zoom feature usually changes the default font size for the entire document.
QFont font = ui->textEdit->document()->defaultFont();
int currentSize = font.pointSize();
// 폰트 크기가 포인트 단위로 설정되지 않았을 경우 (예: 픽셀) 또는 0일 경우,
// 적절한 기본값(예: 12)을 사용합니다.
// If the font size is not set in points (e.g., pixels) or is 0,
// use an appropriate default value (e.g., 12).
if (currentSize <= 0) {
currentSize = 12;
}
// 줌 단계 설정 및 최대 크기 제한 (Max Zoom)
// Set zoom level and limit maximum size (Max Zoom)
const int ZOOM_STEP = 2; // 확대 단위를 2pt로 설정 / Set the zoom unit to 2pt
const int MAX_FONT_SIZE = 48; // 최대 폰트 크기를 48pt로 제한 / Limit maximum font size to 48pt
if (currentSize < MAX_FONT_SIZE) {
int newSize = qMin(currentSize + ZOOM_STEP, MAX_FONT_SIZE);
// 새 폰트 크기 적용 / Apply new font size
font.setPointSize(newSize);
ui->textEdit->document()->setDefaultFont(font);
// 텍스트 전체에 새 폰트 크기 적용 (기존 HTML 로드 시 인라인 스타일이 적용되어 있다면 필요)
// Apply new font size to all text (needed if inline styles were applied when loading existing HTML)
QTextCursor cursor(ui->textEdit->document());
cursor.select(QTextCursor::Document);
QTextCharFormat fmt;
fmt.setFontPointSize(newSize);
// 포맷을 병합하여 폰트 크기만 변경하고 다른 스타일은 유지합니다.
// Merge formats to change only the font size and keep other styles.
cursor.mergeCharFormat(fmt);
cursor.clearSelection();
qDebug() << "Zoom In to size:" << newSize;
// (옵션) 줌 상태 표시 업데이트
// (Optional) Update the zoom status display
// updateZoomStatus(newSize);
} else {
qDebug() << "Maximum zoom level reached.";
}
}
// 줌아웃 커스텀 / Zoom out coustom
void MainWindow::actionZoomOut()
{
// ui->textEdit이 유효한지 확인합니다.
// Check if ui->textEdit is valid.
if (!ui->textEdit) {
return;
}
// 현재 문서의 기본 폰트 정보를 가져옵니다.
// Get the default font information for the current document.
QFont font = ui->textEdit->document()->defaultFont();
int currentSize = font.pointSize();
// 폰트 크기가 포인트 단위로 설정되지 않았거나 0일 경우, 적절한 기본값(예: 12)을 사용합니다.
// If the font size is not set in points or is 0, a reasonable default (e.g. 12) is used.
if (currentSize <= 0) {
currentSize = 12;
}
// 줌 단계 설정 및 최소 크기 제한 (Min Zoom)
// Set zoom level and minimum size limit (Min Zoom)
const int ZOOM_STEP = 2; // 축소 단위를 2pt로 설정 (줌 인과 동일하게 유지) / Set the zoom unit to 2pt (keep it the same as zoom in)
const int MIN_FONT_SIZE = 8; // 최소 폰트 크기를 8pt로 제한 / Limit minimum font size to 8pt
if (currentSize > MIN_FONT_SIZE) {
// 현재 크기에서 줌 단계만큼 빼고, 최소 크기(MIN_FONT_SIZE)보다 작아지지 않도록 제한합니다.
// Subtract the zoom level from the current size, and limit it to no smaller than the minimum size (MIN_FONT_SIZE).
int newSize = qMax(currentSize - ZOOM_STEP, MIN_FONT_SIZE);
// 새 폰트 크기 적용 / Apply new font size
font.setPointSize(newSize);
ui->textEdit->document()->setDefaultFont(font);
// 텍스트 전체에 새 폰트 크기 적용 (줌 아웃이 뷰포트뿐만 아니라 실제 폰트 크기에 반영되도록)
// Apply the new font size to the entire text (so that zooming out reflects the actual font size, not just the viewport)
QTextCursor cursor(ui->textEdit->document());
cursor.select(QTextCursor::Document);
QTextCharFormat fmt;
fmt.setFontPointSize(newSize);
// 포맷을 병합하여 폰트 크기만 변경하고 다른 스타일은 유지합니다. / Merge formats to change only the font size and keep other styles.
cursor.mergeCharFormat(fmt);
cursor.clearSelection();
qDebug() << "Zoom Out to size:" << newSize;
// 줌 상태 표시 업데이트 / Updated zoom status display
// updateZoomStatus(newSize);
} else {
qDebug() << "Minimum zoom level reached.";
}
}
// 줌 초기화 / Reset zoom
void MainWindow::actionZoomReset()
{
// ui->textEdit이 유효한지 확인합니다.
// Check if ui->textEdit is valid.
if (!ui->textEdit) {
return;
}
// 기본 폰트 크기를 가져옵니다. / Get the default font size.
const int newSize = m_defaultPointSize;
// 문서의 기본 폰트를 설정합니다. / Sets the default font for the document.
QFont font = ui->textEdit->document()->defaultFont();
font.setPointSize(newSize);
ui->textEdit->document()->setDefaultFont(font);
// 텍스트 전체에 서식 일괄 적용 (인라인 스타일을 덮어쓰기 위함)
// Apply formatting to all text (to overwrite inline styles)
QTextCursor cursor(ui->textEdit->document());
cursor.select(QTextCursor::Document);
QTextCharFormat fmt;
fmt.setFontPointSize(newSize);
// 포맷을 병합하여 폰트 크기만 기본값으로 변경합니다.
// Merge formats to only change the font size to default.
cursor.mergeCharFormat(fmt);
cursor.clearSelection();
qDebug() << "Zoom Reset to default size:" << newSize;
// 줌 상태 표시 업데이트 / Updated zoom status display
// updateZoomStatus(newSize);
}
// == 텍스트 기능 구현 슬롯 / Text function implementation slot ==
// 굵게 버튼이 클릭될 때 호출되는 슬롯 / A slot called when the bold button is clicked
// 볼드체 적용 슬롯 구현 / Implementing a bold font slot
void MainWindow::actionBold(bool checked)
{
// 새로운 문자 서식 객체를 생성 / Create a new character format object
QTextCharFormat format;
// QAction의 체크 상태(눌림 상태)에 따라 굵게/보통을 설정
// Set bold/normal depending on the checked state (pressed state) of QAction
// QAction::triggered(bool checked) 시그널의 'checked' 값을 사용합니다.
// Use the 'checked' value of the QAction::triggered(bool checked) signal.
format.setFontWeight(checked ? QFont::Bold : QFont::Normal);
// 서식을 텍스트 편집기에 병합 (선택 영역 또는 커서 위치의 단어에 적용)
// Merge formatting into text editor (applies to selection or word at cursor position)
// 이 코드는 QTextEdit::mergeCurrentCharFormat()을 사용합니다.
// This code uses QTextEdit::mergeCurrentCharFormat().
ui->textEdit->mergeCurrentCharFormat(format);
// QAction의 상태는 연결된 QToolButton에 의해 자동으로 토글됩니다.
// The state of a QAction is automatically toggled by the connected QToolButton.
}
// 이탤릭 버튼이 클릭될 때 호출되는 슬롯 / A slot called when the italic button is clicked.
void MainWindow::actionItalic(bool checked)
{
QTextCharFormat format;
// checked 상태에 따라 이탤릭 속성 설정 / Set italic property based on checked state
format.setFontItalic(checked);
ui->textEdit->mergeCurrentCharFormat(format);
}
// 밑줄 버튼이 클릭될 때 호출되는 슬롯
// Slot called when the underline button is clicked
void MainWindow::actionUnderline(bool checked)
{
QTextCharFormat format;
// checked 상태에 따라 밑줄 속성 설정
// Set the underline property according to the checked state
format.setFontUnderline(checked);
ui->textEdit->mergeCurrentCharFormat(format);
}
// 취소선 버튼이 클릭될 때 호출되는 슬롯
// Slot called when the cancel button is clicked
void MainWindow::actionStrike(bool checked)
{
QTextCharFormat format;
// checked 상태에 따라 취소선 속성 설정
// Set strikethrough property based on checked status
format.setFontStrikeOut(checked);
ui->textEdit->mergeCurrentCharFormat(format);
}
// 글꼴 색상 버튼이 클릭될 때 호출되는 슬롯 (수동연결 사용시)
// Slot called when the font color button is clicked (when using manual linking)
void MainWindow::actionColor()
{
// 현재 텍스트 색상을 기본값으로 사용 / Use current text color as default
QColor initialColor = ui->textEdit->currentCharFormat().foreground().color();
QColor color = QColorDialog::getColor(initialColor, this, tr("글꼴 색상 선택"));
if (color.isValid()) {
QTextCharFormat format;
// 선택된 색상을 전경색(글꼴 색상)으로 설정 / Set the selected color as the foreground color (font color)
format.setForeground(QBrush(color));
ui->textEdit->mergeCurrentCharFormat(format);
}
}
// 글꼴 선택 버튼이 클릭될 때 호출되는 슬롯 (수동연결 사용시)
// Slot called when the font selection button is clicked (when using manual linking)
void MainWindow::actionFont()
{
// 현재 폰트를 기본값으로 사용 / Use the current font as default
QFont initialFont = ui->textEdit->currentCharFormat().font();
bool ok;
QFont font = QFontDialog::getFont(&ok, initialFont, this, tr("글꼴 선택"));
if (ok) {
QTextCharFormat format;
// 선택된 폰트를 적용 / Apply selected font
format.setFont(font);
ui->textEdit->mergeCurrentCharFormat(format);
}
}
// == 자동연결은 경고 발생함 ==
// void MainWindow::on_actionColor_triggered()
// {
// // 현재 텍스트 색상을 기본값으로 사용
// QColor initialColor = ui->textEdit->currentCharFormat().foreground().color();
// QColor color = QColorDialog::getColor(initialColor, this, tr("글꼴 색상 선택"));
// if (color.isValid()) {
// QTextCharFormat format;
// // 선택된 색상을 전경색(글꼴 색상)으로 설정
// format.setForeground(QBrush(color));
// ui->textEdit->mergeCurrentCharFormat(format);
// }
// }
// void MainWindow::on_actionFont_triggered()
// {
// // 현재 폰트를 기본값으로 사용
// QFont initialFont = ui->textEdit->currentCharFormat().font();
// bool ok;
// QFont font = QFontDialog::getFont(&ok, initialFont, this, tr("글꼴 선택"));
// if (ok) {
// QTextCharFormat format;
// // 선택된 폰트를 적용
// format.setFont(font);
// ui->textEdit->mergeCurrentCharFormat(format);
// }
// }
// == 이미지 삽입 슬롯 / Image insertion slot ==
void MainWindow::actionInsertImage()
{
QString imagePath = QFileDialog::getOpenFileName(this,
tr("이미지 삽입"),
QDir::homePath(),
tr("이미지 파일 (*.png *.jpg *.jpeg *.gif *.bmp)"));
if (!imagePath.isEmpty()) {
QImage image(imagePath);
if (image.isNull()) {
QMessageBox::critical(this, tr("오류"), tr("이미지 파일을 로드할 수 없습니다."));
return;
}
QTextDocument *doc = ui->textEdit->document();
QUrl imageUrl = QUrl::fromLocalFile(imagePath);
doc->addResource(QTextDocument::ImageResource, imageUrl, image);
QTextImageFormat imageFormat;
// 기본 삽입 크기를 원본 크기로 설정 / Set default insert size to original size
imageFormat.setWidth(image.width());
imageFormat.setHeight(image.height());
imageFormat.setName(imageUrl.toString());
QTextCursor cursor = ui->textEdit->textCursor();
cursor.insertImage(imageFormat);
}
}
// == 이벤트 필터 구현 (이미지 드래그 리사이즈) / Implementing an event filter (image drag resize) ==
bool MainWindow::eventFilter(QObject *obj, QEvent *event)
{
if (obj == ui->textEdit->viewport()) {
QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);
QTextCursor cursor = ui->textEdit->textCursor();
QTextCharFormat format = cursor.charFormat();
// 마우스 누름 이벤트 (Resize 시작 감지) / Mouse press event (detect resize start)
if (event->type() == QEvent::MouseButtonPress && mouseEvent->button() == Qt::LeftButton) {
if (format.isImageFormat()) {
// 이미지가 선택된 위치에서 클릭이 발생한 경우 / If a click occurs at the location where the image is selected
m_isResizing = true;
m_dragStartPos = mouseEvent->pos();
// 현재 이미지 포맷에서 기존 크기를 가져옵니다. / Get the original size from the current image format.
QTextImageFormat imageFormat = format.toImageFormat();
m_originalImageWidth = imageFormat.width();
m_originalImageHeight = imageFormat.height();
// 리사이즈 중에는 마우스 이벤트를 QTextEdit으로 전달하지 않습니다.
// Mouse events are not passed to QTextEdit during resizing.
return true;
}
}
// 마우스 이동 이벤트 (Resize 진행) / Mouse move event (Resize in progress)
else if (event->type() == QEvent::MouseMove) {
if (m_isResizing && mouseEvent->buttons() & Qt::LeftButton) {
// X축 이동 거리를 기반으로 크기를 조절합니다. / Scales based on the distance moved along the X-axis.
int deltaX = mouseEvent->pos().x() - m_dragStartPos.x();
// 새로운 너비 계산 (최소 50픽셀 제한) / New width calculation (minimum 50 pixels limit)
int newWidth = qMax(50, m_originalImageWidth + deltaX);
// 종횡비 유지하여 새로운 높이 계산 / Calculate new height while maintaining aspect ratio
// 소수점 연산을 위해 double로 캐스팅 / Cast to double for decimal operations
int newHeight = (int)((double)m_originalImageHeight * ((double)newWidth / m_originalImageWidth));
// 현재 커서 위치에 있는 이미지 포맷을 업데이트합니다.
// Updates the image format at the current cursor position.
QTextCursor currentCursor = ui->textEdit->textCursor();
if (currentCursor.charFormat().isImageFormat()) {
QTextImageFormat newImageFormat = currentCursor.charFormat().toImageFormat();
newImageFormat.setWidth(newWidth);
newImageFormat.setHeight(newHeight);
// 서식 적용 / Apply Formatting
ui->textEdit->mergeCurrentCharFormat(newImageFormat);
}
return true; // 이벤트를 소비하여 텍스트 선택 등 기본 동작 방지 / Consuming events to prevent default actions such as text selection
}
}
// 마우스 놓기 이벤트 (Resize 종료) / Mouse release event (Resize ends)
else if (event->type() == QEvent::MouseButtonRelease) {
if (m_isResizing && mouseEvent->button() == Qt::LeftButton) {
m_isResizing = false;
// 리사이즈 종료 / End of resize
return true;
}
}
}
// 필터링하지 않은 모든 이벤트를 대상 객체로 전달합니다.
// Forward all unfiltered events to the target object.
return QMainWindow::eventFilter(obj, event);
}
// 커서 위치의 문자 서식이 바뀔 때 호출되는 슬롯
// Slot called when the character format at the cursor position changes
// 툴바 상태 업데이트 슬롯 구현 / Implementing a Toolbar State Update Slot
void MainWindow::updateFormat(const QTextCharFormat &format)
{
// 현재 서식에서 폰트의 굵기를 확인 / Check the font weight in the current format
bool isBold = format.font().bold();
// -- actionBold의 체크 상태를 폰트의 굵기 상태에 맞게 업데이트 / Update the check state of actionBold to match the bold state of the font.
// -- 이렇게 해야 사용자가 굵은 글씨로 커서를 옮기면 버튼이 눌린 상태로 보입니다. / This way, when the user moves the cursor over the bold text, the button appears pressed.
ui->actionBold->setChecked(isBold);
// 다른 서식(Italic, Underline)에 대해서도 이 함수 내에서 동일하게 처리할 수 있습니다.
// You can handle other formats (Italic, Underline) in the same way within this function.
// ui->actionItalic->setChecked(format.font().italic());
// 이탤릭체 상태 업데이트 / Italic status updates
bool isItalic = format.font().italic();
ui->actionItalic->setChecked(isItalic);
// 밑줄 상태 업데이트 / Underline status update
bool isUnderline = format.font().underline();
ui->actionUnderline->setChecked(isUnderline);
// 취소선 상태 업데이트 / Strikethrough status update
bool isStrike = format.font().strikeOut();
ui->actionStrike->setChecked(isStrike);
}
// == 도움말 & Help 기능 / Help & Help function ==
void MainWindow::actionHelp()
{
QMessageBox::information(this,
tr("도움말/Help"),
tr("<h3>간단한 텍스트 편집기 사용법/How to use a simple text editor</h3>"
"<p><strong>파일 메뉴:</strong> 새 파일, 열기, 저장, PDF/Base64 내보내기를 지원합니다.</p>"
"<p><strong>File Menu:</strong> Supports New File, Open, Save, and PDF/Base64 Export.</p>"
"<p><strong>편집 메뉴:</strong> 찾기 및 바꾸기 기능을 사용할 수 있습니다.</p>"
"<p><strong>Edit menu:</strong> You can use the Find and Replace feature.</p>"
"<p><strong>뷰 메뉴:</strong> 텍스트 줌인/줌아웃/초기화를 할 수 있습니다.</p>"
"<p><strong>View Menu:</strong> You can zoom in/out/reset the text.</p>"
"<p><strong>서식 지정:</strong> 텍스트를 굵게, 기울임꼴, 밑줄, 취소선으로 설정하고, 색상 및 폰트를 변경할 수 있습니다.</p>"
"<p><strong>Formatting:</strong> You can make text bold, italic, underline, strikethrough, and change the color and font.</p>"
"<p><strong>이미지:</strong> 로컬 이미지를 삽입하고 마우스로 크기를 조절할 수 있습니다.</p>"
"<p><strong>Image:</strong> Insert a local image and resize it with your mouse.</p>"
),
QMessageBox::Ok);
}
// about slot
void MainWindow::actionAbout()
{
QMessageBox::about(this,
tr("Rich Text Editor 정보/Rich Text Editor information"),
tr("<h2>Rich Text Editor v1.0 (Qt)</h2>"
"<p>Qt 프레임워크를 기반으로 개발된 간단한 텍스트 편집기입니다.</p>"
"<p>A Rich text editor developed based on the Qt framework.</p>"
"<p>주요 기능: 파일 입출력, 텍스트 서식 지정, 이미지 삽입 및 크기 조절.</p>"
"<p>Key features: file input/output, text formatting, image insertion and resizing.</p>"
"<p>Copyright © 2025</p>"
));
}
// 메모리해제 / "<p>Key features: file input/output, text formatting, image insertion and resizing.</p>"
MainWindow::~MainWindow()
{
delete ui;
}