如何实现一个可视化的文字编辑器(C语言版)?

一、软件安装

Visual Studio 2022

Visual Studio 2022 是微软提供的强大集成开发环境(IDE),广泛用于C/C++、C#、Python等多种编程语言的开发。它提供了许多强大的工具,帮助开发者编写、调试和优化代码。

1.下载 Visual Studio 2022:访问Visual Studio 官网

2.点击 Download Visual Studio,选择 Community Edition(免费)或其他版本,根据需求选择。

2.安装 Visual Studio:运行下载的安装程序,启动 Visual Studio 安装程序。

在安装界面选择 Desktop development with C++,开发 C++ 程序所需的工作负载。

可以根据需要选择其他组件,比如 Windows 10 SDK、C++ CMake tools 等,帮助进行图形和图像处理的开发,完成安装后,启动 Visual Studio 2022,选择 Create a new project 来创建新的C++项目。(不会安装的可以搜一下相关教程)

安装 EasyX 图形库

1.下载 EasyX 图形库:EasyX官网,根据需要选择合适版本。

2.安装 EasyX:

二、效果演示

1.界面样式

2.读入文件

3.查找

4.插入文字

5.替换

三、代码实现

Text.h:该文件是 Text.cpp 的头文件,声明了文本操作相关的结构体和函数接口。

  • Node 结构体:表示链表中的一个字符节点。包含字符内容、字符在屏幕上的位置、是否为普通字符、是否被选中等信息。

  • Text 结构体:表示文本内容,包含头指针、尾指针和当前光标位置。

  • File 结构体:表示文件,包含文件路径和文本内容。

  • 函数声明:如 readFile()writeFile()insertText()deleteCharacter() 等。

cpp 复制代码
#pragma once
/*
该模块用于文本的操作
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>

// 该结构体表示链表结点
typedef struct Node Node;
struct Node {
	char content[3]; // 表示一个普通字符或中午字符
	int rect[4]; // 字符在屏幕上所占的区域
	bool normal; // 是否是普通字符
	bool selected; // 是否被选中
	Node* next;
	Node* prev;
};

// 该结构体表示文本
typedef struct Text Text;
struct Text {
	Node* head; // 头指针
	Node* tail; // 尾指针
	Node* cursor; // 当前指针位置
};

// 该结构体表示文件
typedef struct File File;
struct File {
	char path[256]; // 文件的路径
	Text* text; // 文件的内容
};

// 读入文件
bool readFile(char* path);

// 写入文件
void writeFile();

// 新建文件
bool createFile(char* path);

// 保存文件
bool saveFile(char* path);

// 获取当前文件
File* getCurrentFile();

// 释放相关内存
void freeResources();

// 插入文字
void insertText(char* mContent);

// 删除字符
void deleteCharacter(bool isAfter);

// 重置选中标志
void resetSelected();

// 查找字符串
void findString(char* searchText);

// 替换字符串
void replaceString(char* srcText, char* dstText);

Text.cpp:实现了文本内容的管理与操作,包括文件的读取、保存、文本的插入、删除、查找和替换等。它使用了链表结构来组织文本内容。

  • readFile()

    • 读取指定路径的文件,并将文件内容解析为字符。读取的字符根据其字节数判断是普通字符还是中文字符,并将其存储到链表中。

    • 使用 fread() 函数按字节读取文件内容,若字符为普通字符,则将其存储在 content[] 中,若为中文字符,则按两个字节读取。

  • writeFile()

    • 将当前内存中的文本内容(链表结构)保存到文件中。通过 fopen() 打开文件,并逐个节点将文本内容写入。
  • createFile()

    • 创建新文件并为其分配内存。调用 writeFile() 保存当前文件内容,释放内存,创建新文件,并初始化新的文本结构。
  • insertText()

    • 在当前光标位置插入文本。通过 appendString() 函数将新的文本插入到链表的对应位置。
  • deleteCharacter()

    • 删除当前光标位置的字符。通过检查光标的位置(isAfter 参数决定删除前或后字符)来删除指定的字符节点。
  • findString()

    • 查找指定的字符串,并将找到的部分高亮显示。通过遍历链表中的节点,并检查每个节点的 content 是否与目标字符串匹配。
  • replaceString()

    • 查找并替换文本中的指定字符串。通过遍历链表查找目标字符串,若找到则替换为指定的新字符串。
cpp 复制代码
#include "Text.h"

// 在某个节点后添加字符串
void appendString(Node** beforeNode, char* mContent, bool modifyBeforeNode);

// 当前的文件
static File* curFile = NULL;

// 为新文件分配内容
static void initFile(File* file, char* path) {
	strcpy(curFile->path, path);
	curFile->text = (Text*)calloc(1, sizeof(Text));
	curFile->text->head = (Node*)calloc(1, sizeof(Node));
	curFile->text->tail = (Node*)calloc(1, sizeof(Node));
	curFile->text->head->next = curFile->text->tail;
	curFile->text->tail->prev = curFile->text->head;
	curFile->text->cursor = curFile->text->head;
}

// 释放文件占用的内存
static void destroyFile(File** file) {
	while ((*file)->text->head->next != (*file)->text->tail) {
		Node* node = (*file)->text->head->next;
		(*file)->text->head->next = node->next;
		free(node);
	}
	free((*file)->text->head);
	free((*file)->text->tail);
	free(*file);
	*file = NULL;
}

// 在Text中添加一个Node
static void insertNode(Node* beforeNode, char content[], bool normal) {
	Node* node = (Node*)calloc(1, sizeof(Node));
	strcpy(node->content, content);
	node->normal = normal;
	node->selected = false;
	node->next = beforeNode->next;
	node->prev = beforeNode;
	beforeNode->next->prev = node;
	beforeNode->next = node;
}

// 读入文件
bool readFile(char* path) {
	// 确保文件存在
	FILE* file = fopen(path, "rb");
	if (!file) {
		return false;
	}
	
	// 保存当前文件
	writeFile();

	// 释放内存
	if (curFile) {
		destroyFile(&curFile);
	}

	// 为新文件分配内存
	curFile = (File*)calloc(1, sizeof(File));
	initFile(curFile, path);

	// 读取新的文件
	char byte;
	char content[3] = {0};
	bool normal;
	while (fread(&byte, 1, 1, file) == 1) {
		if (byte >= 0) {
			// 这是普通字符
			content[0] = byte;
			if (byte == '\r') {
				char nextByte;
				fread(&nextByte, 1, 1, file);
				content[1] = nextByte;
				normal = false;
			}
			else {
				content[1] = '\0';
				normal = true;
			}
		}
		else {
			// 这是中文,其占2个字节
			char nextByte;
			fread(&nextByte, 1, 1, file);
			content[0] = byte;
			content[1] = nextByte;
			normal = false;
		}

		// 添加当前普通字符或者中文字符到文件的文本中
		insertNode(curFile->text->tail->prev, content, normal);
	}
	fclose(file);

	return true;
}

// 写入文件
void writeFile() {
	if (!curFile) return;
	
	// 把新内容写入文件
	FILE* fp = fopen(curFile->path, "w");

	Node* node = curFile->text->head->next;
	while (node != curFile->text->tail) {
		fprintf(fp, "%s", node->content);
		node = node->next;
	}

	fclose(fp);
}

// 新建文件
bool createFile(char* path) {
	// 保存当前文件
	writeFile();

	// 释放内存
	if (curFile) {
		destroyFile(&curFile);
	}

	FILE* fp = fopen(path, "w");
	if (!fp) return false;
	fclose(fp);

	// 为新文件分配内存
	curFile = (File*)calloc(1, sizeof(File));
	initFile(curFile, path);

	return true;
}

// 保存文件
bool saveFile(char* path) {
	if (!curFile) return false;

	// 把内容写入新的文件
	FILE* fp = fopen(path, "w");
	if (!fp) return false;

	Node* node = curFile->text->head->next;
	while (node != curFile->text->tail) {
		fprintf(fp, "%s", node->content);
		node = node->next;
	}

	fclose(fp);
	return true;
}

// 获取当前文件
File* getCurrentFile() {
	return curFile;
}

// 释放相关内存
void freeResources() {
	if (curFile) {
		destroyFile(&curFile);
	}
}

// 插入文字
void insertText(char* mContent) {
	if (!curFile || !mContent || strlen(mContent) == 0) return;

	// 添加字符串到当前指针后面
	appendString(&curFile->text->cursor, mContent, true);
}

// 删除当前字符
void deleteCharacter(bool isAfter) {
	if (curFile) {
		if (isAfter) {
			// 删除指针后面的字符
			Node* node = curFile->text->cursor->next;
			if (node != curFile->text->tail) {
				curFile->text->cursor->next = node->next;
				node->next->prev = curFile->text->cursor;
				free(node);
			}
		}
		else {
			// 删除指针指向的字符
			Node* node = curFile->text->cursor;
			if (node != curFile->text->head) {
				curFile->text->cursor = node->prev;
				curFile->text->cursor->next = node->next;
				node->next->prev = curFile->text->cursor;
				free(node);
			}
		}
	}
}

// 重置选中标志
void resetSelected() {
	if (!curFile) return;
	Node* node = curFile->text->head->next;
	while (node != curFile->text->tail) {
		node->selected = false;
		node = node->next;
	}
}


// 查找字符串
void findString(char* searchText) {
	if (!curFile) return;

	char content[3] = { 0 };
	char byte;
	size_t len = strlen(searchText);
	Node* curNode = curFile->text->head->next;
	while (curNode != curFile->text->tail) {
		Node* tmpNode = curNode;
		bool isFound = true;
		// 查找子串
		for (int i = 0; i < len && tmpNode != curFile->text->tail; i++) {
			byte = searchText[i];
			if (byte >= 0) {
				// 这是普通字符
				content[0] = byte;
				if (byte == '\r') {
					char nextByte = searchText[++i];
					content[1] = nextByte;
				}
				else {
					content[1] = '\0';
				}
			}
			else {
				// 这是中文,其占2个字节
				char nextByte = searchText[++i];
				content[0] = byte;
				content[1] = nextByte;
			}
			// 判断两个字符是否相同
			if (strcmp(tmpNode->content, content) != 0) {
				isFound = false;
				break;
			}
			tmpNode = tmpNode->next;
		}
		if (isFound) {
			// 设置对应的字符序列为选中
			Node* node = curNode;
			while (node != tmpNode) {
				node->selected = true;
				node = node->next;
			}
		}
		curNode = curNode->next;
	}
}

// 在某个节点后添加字符串
void appendString(Node** beforeNode, char* mContent, bool modifyBeforeNode) {
	int i = 0;
	char byte;
	bool normal;
	char content[3] = { 0 };
	size_t len = strlen(mContent);
	Node* prev = *beforeNode;
	while (i < len) {
		byte = mContent[i];
		if (byte >= 0) {
			// 这是普通字符
			content[0] = byte;
			if (byte == '\r') {
				char nextByte = mContent[++i];
				content[1] = nextByte;
				normal = false;
			}
			else {
				content[1] = '\0';
				normal = true;
			}
		}
		else {
			// 这是中文,其占2个字节
			char nextByte = mContent[++i];
			content[0] = byte;
			content[1] = nextByte;
			normal = false;
		}

		// 添加当前普通字符或者中文字符到该指针后面
		insertNode(prev, content, normal);
		prev = prev->next;
		if (modifyBeforeNode) {
			*beforeNode = prev;
		}

		i++;
	}
}

// 替换字符串
void replaceString(char* srcText, char* dstText) {
	if (!curFile) return;

	char content[3] = { 0 };
	char byte;
	size_t len = strlen(srcText);
	Node* curNode = curFile->text->head->next;
	while (curNode != curFile->text->tail) {
		Node* tmpNode = curNode;
		bool isFound = true;
		// 查找子串
		for (int i = 0; i < len && tmpNode != curFile->text->tail; i++) {
			byte = srcText[i];
			if (byte >= 0) {
				// 这是普通字符
				content[0] = byte;
				if (byte == '\r') {
					char nextByte = srcText[++i];
					content[1] = nextByte;
				}
				else {
					content[1] = '\0';
				}
			}
			else {
				// 这是中文,其占2个字节
				char nextByte = srcText[++i];
				content[0] = byte;
				content[1] = nextByte;
			}
			// 判断两个字符是否相同
			if (strcmp(tmpNode->content, content) != 0) {
				isFound = false;
				break;
			}
			tmpNode = tmpNode->next;
		}
		if (isFound) {
			// 首先删除原字符串
			Node* beforeNode = curNode->prev;
			while (beforeNode->next != tmpNode) {
				Node* node = beforeNode->next;
				beforeNode->next = node->next;
				node->next->prev = beforeNode;
				free(node);
			}
			curNode = tmpNode;

			// 添加替换字符串
			appendString(&beforeNode, dstText, false);
		}
		else {
			curNode = curNode->next;
		}
	}
}

Editor.h:是 Editor.cpp 的头文件,声明了文本编辑器相关的函数接口,确保其他文件能够调用 Editor.cpp 中定义的函数。

声明了文本编辑器相关的函数,如:

  • initTextEditor():初始化编辑器。

  • runTextEditor():运行编辑器的主循环。

  • destroyTextEditor():销毁编辑器。

cpp 复制代码
#pragma once
/*
该模块用于显示文本编辑器的界面和用户交互
*/

// 初始化文本编辑器
void initTextEditor();

// 运行文本编辑器
void runTextEditor();

// 销毁文本编辑器
void destroyTextEditor();

Editor.cpp:文本编辑器的核心,负责创建并显示图形界面,同时处理文件操作、文本编辑、用户输入事件等。它利用 EasyX 图形库来绘制窗口和菜单,支持文件的读写操作、文本的插入与删除、文本的查找和替换。

  • initTextEditor()

    • 初始化文本编辑器,创建窗口并设置背景色。

    • 使用 initgraph() 函数来初始化窗口,setbkcolor() 设置背景色为白色,SetWindowText() 设置窗口的标题为 "文本编辑器"。

    • 还通过 readResources() 函数加载图像资源(如 editor.png),并设置图形界面的直线样式。

  • runTextEditor()

    • 启动文本编辑器的主循环。在这个循环中,程序不断更新窗口,清除设备并重新绘制所有组件(如菜单和文本区域)。

    • 处理键盘事件(如删除字符)和鼠标点击事件(如选择菜单项):

      • 键盘事件:当按下删除键(VK_BACK)时,删除当前光标位置的字符。

      • 鼠标事件:检测鼠标点击的区域,根据用户点击的菜单项执行不同操作,如读入文件、写入文件、新建文件等。

  • drawWindow()

    • 用于绘制编辑器窗口,包括背景图像、菜单区域和文本区域。

    • 使用 putimage() 函数绘制背景图像,并调用 drawMenu()drawText() 来绘制菜单和文本区域。

  • drawMenu()

    • 绘制顶部的菜单区域,包括文件操作(如读入、写入、保存等)和文本编辑操作(如插入文本、查找、替换)。

    • 使用 outtextxy() 函数绘制菜单项文本,并用 roundrect() 绘制按钮。

  • InsertText()

    • 提示用户输入要插入的文字,并调用 insertText() 函数将文字插入到当前光标位置。
  • FindString()

    • 查找字符串。调用 findString() 函数查找指定的字符串并将找到的文本高亮显示。
  • ReplaceString()

    • 替换字符串。通过 replaceString() 函数,查找指定的原字符串并将其替换为新字符串。
cpp 复制代码
#include "Editor.h"
#include <graphics.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <time.h>
#include <math.h>
#include "Text.h"

// 窗口大小
static int WINDOW_WIDTH = 1000;
static int WINDOW_HEIGHT = 700;

// 设置每帧的时间
static double FPS = 30;
static double PER_FRAME_TIME = 1000.0 / FPS;

// 查找字符串
static char searchText[256] = {0};

// 原字符串
static char sourceText[256] = { 0 };

// 替换字符串
static char replaceText[256] = { 0 };

// 设置菜单区域的位置
static int MENU_LEFT_TOP[] = { 10, 80 };
static int MENU_RIGHT_BOTTOM[] = { 900, 220 };
static int PADDING[] = {50, 16};

// 读入文件菜单位置
static int READ_FILE_MENU[] = { MENU_LEFT_TOP[0] + PADDING[0], MENU_LEFT_TOP[1] + PADDING[1],
						MENU_LEFT_TOP[0] + PADDING[0] + 104, MENU_LEFT_TOP[1] + PADDING[1] + 25 };
// 写入文件菜单位置
static int WRITE_FILE_MENU[] = { READ_FILE_MENU[2] + PADDING[0], MENU_LEFT_TOP[1] + PADDING[1],
						READ_FILE_MENU[2] + PADDING[0] + 104, MENU_LEFT_TOP[1] + PADDING[1] + 25 };
// 新建文件菜单位置
static int CREATE_FILE_MENU[] = { WRITE_FILE_MENU[2] + PADDING[0], MENU_LEFT_TOP[1] + PADDING[1],
						WRITE_FILE_MENU[2] + PADDING[0] + 104, MENU_LEFT_TOP[1] + PADDING[1] + 25 };
// 保存文件菜单位置
static int SAVE_FILE_MENU[] = { CREATE_FILE_MENU[2] + PADDING[0], MENU_LEFT_TOP[1] + PADDING[1],
						CREATE_FILE_MENU[2] + PADDING[0] + 104, MENU_LEFT_TOP[1] + PADDING[1] + 25 };
// 退出菜单位置
static int EXIT_MENU[] = { SAVE_FILE_MENU[2] + PADDING[0], MENU_LEFT_TOP[1] + PADDING[1],
						SAVE_FILE_MENU[2] + PADDING[0] + 52, MENU_LEFT_TOP[1] + PADDING[1] + 25 };
// 编辑文件菜单位置
static int EDIT_FILE_MENU[] = { MENU_LEFT_TOP[0] + PADDING[0], READ_FILE_MENU[3] + PADDING[1],
						MENU_LEFT_TOP[0] + PADDING[0] + 104, READ_FILE_MENU[3] + PADDING[1] + 25 };
// 插入菜单位置
static int INSERT_TEXT_MENU[] = { MENU_LEFT_TOP[0] + PADDING[0], EDIT_FILE_MENU[3] + PADDING[1],
						MENU_LEFT_TOP[0] + PADDING[0] + 104, EDIT_FILE_MENU[3] + PADDING[1] + 25 };
// 查找文本位置
static int SEARCH_TEXT[] = { EDIT_FILE_MENU[2] + PADDING[0], READ_FILE_MENU[3] + PADDING[1],
						EDIT_FILE_MENU[2] + PADDING[0] + 208, READ_FILE_MENU[3] + PADDING[1] + 25 };
// 查找菜单位置
static int FIND_MENU[] = { SEARCH_TEXT[2] + PADDING[0], READ_FILE_MENU[3] + PADDING[1],
						SEARCH_TEXT[2] + PADDING[0] + 52, READ_FILE_MENU[3] + PADDING[1] + 25 };
// 清除高亮菜单位置
static int CLEAR_MENU[] = { FIND_MENU[2] + PADDING[0], READ_FILE_MENU[3] + PADDING[1],
						FIND_MENU[2] + PADDING[0] + 104, READ_FILE_MENU[3] + PADDING[1] + 25 };
// 原文本位置
static int SOURCE_TEXT[] = { EDIT_FILE_MENU[2] + PADDING[0], FIND_MENU[3] + PADDING[1],
						EDIT_FILE_MENU[2] + PADDING[0] + 208, FIND_MENU[3] + PADDING[1] + 25 };
// 替换文本位置
static int REPLACE_TEXT[] = { SOURCE_TEXT[2] + PADDING[0], FIND_MENU[3] + PADDING[1],
						SOURCE_TEXT[2] + PADDING[0] + 208, FIND_MENU[3] + PADDING[1] + 25 };
// 替换菜单位置
static int REPLACE_MENU[] = { REPLACE_TEXT[2] + PADDING[0], FIND_MENU[3] + PADDING[1],
						REPLACE_TEXT[2] + PADDING[0] + 52, FIND_MENU[3] + PADDING[1] + 25 };
// 文本区域位置
static int TEXT_AREA[] = { 10, MENU_RIGHT_BOTTOM[1] + PADDING[1], 
						WINDOW_WIDTH-20, WINDOW_HEIGHT - 20 };

// 图片资源
static IMAGE editorImage;

// 读取图片资源
static void readResources();

// 绘制窗口
static void drawWindow();

// 绘制菜单区域
static void drawMenu();

// 绘制文本区域
static void drawText();

// 读入文件
static void ReadFile();

// 写入文件
static void WriteFile();

// 新建文件
static void CreateNewFile();

// 保存文件
static void SaveFile();

// 插入文字
static void InsertText();

// 查找
static void FindString();

// 清除输入
static void ClearInput();

// 替换
static void ReplaceString();

// 更新文件指针位置
static void updateCursor(int x, int y);

// 初始化文本编辑器
void initTextEditor() {
	// 创建窗口 
	initgraph(WINDOW_WIDTH, WINDOW_HEIGHT);
	setbkcolor(RGB(255, 255, 255));

	// 设置窗口名称
	HWND hWnd = GetHWnd();
	SetWindowText(hWnd, "文本编辑器");

	// 读取图片资源
	readResources();

	// 设置直线样式
	LINESTYLE style;
	style.thickness = 2;
	setlinestyle(&style);
}

// 运行文本编辑器
void runTextEditor() {
	bool exitCode = false;
	while (!exitCode) {
		int startTime = clock();

		// 双缓冲 
		BeginBatchDraw();
		cleardevice();

		// 绘制窗口
		drawWindow();

		EndBatchDraw();

		// 处理消息
		static ExMessage msg;
		while (peekmessage(&msg, EM_KEY | EM_MOUSE)) {
			// 键盘按下事件
			if (msg.message == WM_KEYDOWN) {
				if (msg.vkcode == VK_BACK) {
					// 删除指针指向的字符
					deleteCharacter(false);
				}
				else if (msg.vkcode == VK_DELETE) {
					// 删除指针后面的字符
					deleteCharacter(true);
				}
			}
			// 鼠标左键按下事件
			else if (msg.message == WM_LBUTTONDOWN) {
				int x = msg.x;
				int y = msg.y;
				// 点击了读入文件按钮
				if (x > READ_FILE_MENU[0] && x < READ_FILE_MENU[2] &&
					y > READ_FILE_MENU[1] && y < READ_FILE_MENU[3]) {
					ReadFile();
				}
				// 点击了写入文件按钮
				else if (x > WRITE_FILE_MENU[0] && x < WRITE_FILE_MENU[2] &&
					y > WRITE_FILE_MENU[1] && y < WRITE_FILE_MENU[3]) {
					WriteFile();
				}
				// 点击了创建文件按钮
				else if (x > CREATE_FILE_MENU[0] && x < CREATE_FILE_MENU[2] &&
					y > CREATE_FILE_MENU[1] && y < CREATE_FILE_MENU[3]) {
					CreateNewFile();
				}
				// 点击了保存文件按钮
				else if (x > SAVE_FILE_MENU[0] && x < SAVE_FILE_MENU[2] &&
					y > SAVE_FILE_MENU[1] && y < SAVE_FILE_MENU[3]) {
					SaveFile();
				}
				// 点击了退出按钮
				else if (x > EXIT_MENU[0] && x < EXIT_MENU[2] &&
					y > EXIT_MENU[1] && y < EXIT_MENU[3]) {
					// 释放相关内存
					freeResources();
					exitCode = true;
					break;
				}
				// 点击了插入文字按钮
				else if (x > INSERT_TEXT_MENU[0] && x < INSERT_TEXT_MENU[2] &&
					y > INSERT_TEXT_MENU[1] && y < INSERT_TEXT_MENU[3]) {
					InsertText();
				}
				// 点击了查找按钮
				else if (x > FIND_MENU[0] && x < FIND_MENU[2] &&
					y > FIND_MENU[1] && y < FIND_MENU[3]) {
					FindString();
				}
				// 点击了清楚高亮按钮
				else if (x > CLEAR_MENU[0] && x < CLEAR_MENU[2] &&
					y > CLEAR_MENU[1] && y < CLEAR_MENU[3]) {
					ClearInput();
				}
				// 点击了替换按钮
				else if (x > REPLACE_MENU[0] && x < REPLACE_MENU[2] &&
					y > REPLACE_MENU[1] && y < REPLACE_MENU[3]) {
					ReplaceString();
				}
				// 点击了文本区域
				else if (x > TEXT_AREA[0] && x < TEXT_AREA[2] &&
					y > TEXT_AREA[1] && y < TEXT_AREA[3]) {
					updateCursor(x, y);
				}
				// 输入查找字符串
				else if (x > SEARCH_TEXT[0] && x < SEARCH_TEXT[2] &&
					y > SEARCH_TEXT[1] && y < SEARCH_TEXT[3]) {
					InputBox(searchText, 255, "输入查找字符串:");
				}
				// 输入原字符串
				else if (x > SOURCE_TEXT[0] && x < SOURCE_TEXT[2] &&
					y > SOURCE_TEXT[1] && y < SOURCE_TEXT[3]) {
					InputBox(sourceText, 255, "输入原字符串:");
				}
				// 输入替换字符串
				else if (x > REPLACE_TEXT[0] && x < REPLACE_TEXT[2] &&
					y > REPLACE_TEXT[1] && y < REPLACE_TEXT[3]) {
					InputBox(replaceText, 255, "输入替换字符串:");
				}
			}
		}

		// 控制每帧时间 
		int duration = clock() - startTime;
		if (duration < PER_FRAME_TIME) {
			Sleep(PER_FRAME_TIME - duration);
		}
	}
}

// 销毁文本编辑器
void destroyTextEditor() {
	closegraph();
}

// 读取图片资源
static void readResources() {
	loadimage(&editorImage, "editor.png");
}

// 绘制窗口
static void drawWindow() {
	// 绘制编辑器图片
	putimage(0, 0, &editorImage);

	// 绘制菜单区域
	drawMenu();

	// 绘制文本区域
	drawText();
}

// 绘制菜单区域
static void drawMenu() {
	// 设置字体
	LOGFONT f;
	gettextstyle(&f);
	f.lfHeight = 25;
	_tcscpy(f.lfFaceName, _T("黑体"));
	f.lfQuality = ANTIALIASED_QUALITY;
	f.lfWeight = 800;
	settextstyle(&f);
	settextcolor(RGB(0, 0, 0));

	// 绘制文件编辑器的菜单
	setlinecolor(RGB(0, 0, 0));
	rectangle(MENU_LEFT_TOP[0], MENU_LEFT_TOP[1], MENU_RIGHT_BOTTOM[0], MENU_RIGHT_BOTTOM[1]);

	// 读入文件菜单
	outtextxy(READ_FILE_MENU[0], READ_FILE_MENU[1], "读入文件");
	roundrect(READ_FILE_MENU[0], READ_FILE_MENU[1], READ_FILE_MENU[2], READ_FILE_MENU[3], 5, 5);

	// 写入文件菜单
	outtextxy(WRITE_FILE_MENU[0], WRITE_FILE_MENU[1], "写入文件");
	roundrect(WRITE_FILE_MENU[0], WRITE_FILE_MENU[1], WRITE_FILE_MENU[2], WRITE_FILE_MENU[3], 5, 5);

	// 新建文件菜单
	outtextxy(CREATE_FILE_MENU[0], CREATE_FILE_MENU[1], "新建文件");
	roundrect(CREATE_FILE_MENU[0], CREATE_FILE_MENU[1], CREATE_FILE_MENU[2], CREATE_FILE_MENU[3], 5, 5);

	// 保存文件菜单
	outtextxy(SAVE_FILE_MENU[0], SAVE_FILE_MENU[1], "保存文件");
	roundrect(SAVE_FILE_MENU[0], SAVE_FILE_MENU[1], SAVE_FILE_MENU[2], SAVE_FILE_MENU[3], 5, 5);

	// 退出菜单
	outtextxy(EXIT_MENU[0], EXIT_MENU[1], "退出");
	roundrect(EXIT_MENU[0], EXIT_MENU[1], EXIT_MENU[2], EXIT_MENU[3], 5, 5);

	// 编辑文件菜单
	outtextxy(EDIT_FILE_MENU[0], EDIT_FILE_MENU[1], "编辑文件");

	// 插入菜单
	outtextxy(INSERT_TEXT_MENU[0], INSERT_TEXT_MENU[1], "插入文字");
	roundrect(INSERT_TEXT_MENU[0], INSERT_TEXT_MENU[1], INSERT_TEXT_MENU[2], INSERT_TEXT_MENU[3], 5, 5);

	// 查找菜单
	outtextxy(FIND_MENU[0], FIND_MENU[1], "查找");
	roundrect(FIND_MENU[0], FIND_MENU[1], FIND_MENU[2], FIND_MENU[3], 5, 5);

	// 清除输入菜单
	outtextxy(CLEAR_MENU[0], CLEAR_MENU[1], "清除输入");
	roundrect(CLEAR_MENU[0], CLEAR_MENU[1], CLEAR_MENU[2], CLEAR_MENU[3], 5, 5);

	// 替换菜单
	outtextxy(REPLACE_MENU[0], REPLACE_MENU[1], "替换");
	roundrect(REPLACE_MENU[0], REPLACE_MENU[1], REPLACE_MENU[2], REPLACE_MENU[3], 5, 5);

	// 设置字体
	gettextstyle(&f);
	f.lfHeight = 18;
	_tcscpy(f.lfFaceName, _T("宋体"));
	f.lfQuality = ANTIALIASED_QUALITY;
	f.lfWeight = 800;
	settextstyle(&f);
	settextcolor(RGB(0, 0, 0));

	// 查找文本
	if (strlen(searchText) > 0) {
		outtextxy(SEARCH_TEXT[0], SEARCH_TEXT[1]+3, searchText);
	}
	roundrect(SEARCH_TEXT[0], SEARCH_TEXT[1], SEARCH_TEXT[2], SEARCH_TEXT[3], 5, 5);

	// 原文本
	if (strlen(sourceText) > 0) {
		outtextxy(SOURCE_TEXT[0], SOURCE_TEXT[1] + 3, sourceText);
	}
	roundrect(SOURCE_TEXT[0], SOURCE_TEXT[1], SOURCE_TEXT[2], SOURCE_TEXT[3], 5, 5);

	// 替换文本
	if (strlen(replaceText) > 0) {
		outtextxy(REPLACE_TEXT[0], REPLACE_TEXT[1] + 3, replaceText);
	}
	roundrect(REPLACE_TEXT[0], REPLACE_TEXT[1], REPLACE_TEXT[2], REPLACE_TEXT[3], 5, 5);
}

// 绘制文本区域
static void drawText() {
	// 设置字体
	LOGFONT f;
	gettextstyle(&f);
	f.lfHeight = 20;
	f.lfWeight = 600;
	_tcscpy(f.lfFaceName, _T("宋体"));
	f.lfQuality = ANTIALIASED_QUALITY;
	settextstyle(&f);
	settextcolor(RGB(0, 0, 0));

	// 显示文本区域框
	rectangle(TEXT_AREA[0], TEXT_AREA[1], TEXT_AREA[2], TEXT_AREA[3]);

	// 显示文件内容
	File* file = getCurrentFile();
	if (!file) return;

	int leftX = TEXT_AREA[0] + 5;
	int x = leftX;
	int y = TEXT_AREA[1] + 5;
	int w = textwidth("a");
	int h = f.lfHeight;
	Node* node = file->text->head->next;

	// 设置头指针的位置
	file->text->head->rect[0] = TEXT_AREA[0]+4;
	file->text->head->rect[1] = y;
	file->text->head->rect[2] = TEXT_AREA[0]+4;
	file->text->head->rect[3] = y;

	// 如果文件是空的,则在开头显示指针
	if (node == file->text->tail) {
		line(node->prev->rect[2], node->prev->rect[1], node->prev->rect[2], node->prev->rect[1] + h);
		return;
	}

	while (node != file->text->tail) {
		// 设置字符所占的区域
		node->rect[0] = x;
		node->rect[1] = y;
		node->rect[2] = x + (node->normal ? w : w*2);
		node->rect[3] = y + h;

		// 判断是否需要换行
		if (strcmp(node->content, "\r\n") == 0 || strcmp(node->content, "\n") == 0 
			|| node->rect[2] >= TEXT_AREA[2]) {
			// 需要换行,更新位置
			x = leftX;
			y += f.lfHeight;
			node->rect[0] = x;
			node->rect[1] = y;
			node->rect[2] = x + (node->normal ? w : w * 2);
			node->rect[3] = y + h;
		}

		if (strcmp(node->content, "\r\n") != 0 && strcmp(node->content, "\n") != 0) {
			// 如果字符被选中,则更改颜色
			if (node->selected) {
				setcolor(RED);
			}
			else {
				setcolor(BLACK);
			}
			// 显示该字符
			outtextxy(node->rect[0], node->rect[1], node->content);
			// 更新x值
			x = node->rect[2];
		}

		// 绘制当前位置指针
		if (file->text->cursor == node->prev) {
			line(node->prev->rect[2], node->prev->rect[1], node->prev->rect[2], node->prev->rect[1] + h);
		}
		else if (file->text->cursor == node && node->next == file->text->tail) {
			line(node->rect[2], node->rect[1], node->rect[2], node->rect[1] + h);
		}

		node = node->next;
	}
}

// 更新文件指针位置
static void updateCursor(int x, int y) {
	File* file = getCurrentFile();
	if (!file) return;

	// 设置文件指针到最靠近鼠标的文字的位置(距离不能超过26)
	int minDistance = INT_MAX;
	Node* node = file->text->head->next;
	while (node != file->text->tail) {
		if (x > node->rect[0] && x < node->rect[2] &&
			y > node->rect[1] && y < node->rect[3]) {
			file->text->cursor = node;
			break;
		}
		else {
			// 判断该文字是否更加靠近鼠标点击的位置
			int dx = (node->rect[0] + node->rect[2]) / 2 - x;
			int dy = (node->rect[1] + node->rect[3]) / 2 - y;
			double dist = sqrt(dx * dx + dy * dy);
			if (dist < 26 && dist < minDistance) {
				minDistance = dist;
				file->text->cursor = node;
			}
		}
		node = node->next;
	}
}

// 读入文件
static void ReadFile() {
	// 提示用户输入文件路径
	char path[256];
	InputBox(path, 255, "输入文件路径:");

	// 读入文件内容
	if (!readFile(path)) {
		MessageBox(GetHWnd(), "该文件不存在", "提示", MB_OK);
	}
}


// 写入文件
static void WriteFile() {
	if (getCurrentFile()) {
		writeFile();
		MessageBox(GetHWnd(), "内容已被写入文件", "提示", MB_OK);
	}
}


// 新建文件
static void CreateNewFile() {
	// 提示用户输入文件路径
	char path[256];
	InputBox(path, 255, "输入文件路径:");

	// 创建新的文件
	if (!createFile(path)) {
		MessageBox(GetHWnd(), "创建文件失败", "提示", MB_OK);
	}
}


// 保存文件
static void SaveFile() {
	// 提示用户输入文件路径
	char path[256];
	InputBox(path, 255, "输入文件路径:");

	if (!saveFile(path)) {
		MessageBox(GetHWnd(), "保存文件失败", "提示", MB_OK);
	}
	else {
		MessageBox(GetHWnd(), "文件已被保存", "提示", MB_OK);
	}
}

// 插入文字
static void InsertText() {
	// 提示输入要插入的文字
	char content[256];
	InputBox(content, 255, "输入要插入的文字:");

	// 把该文字插入指针所在位置
	insertText(content);
}


// 查找
static void FindString() {
	resetSelected();
	if (strlen(searchText) > 0) {
		findString(searchText);
	}
}

// 清除输入
static void ClearInput() {
	memset(searchText, 0, sizeof(searchText));
	memset(sourceText, 0, sizeof(sourceText));
	memset(replaceText, 0, sizeof(replaceText));

	// 重置选中标志
	resetSelected();
}

// 替换
static void ReplaceString() {
	if (strlen(sourceText) > 0 && strlen(replaceText) > 0) {
		replaceString(sourceText, replaceText);
	}
}

Main.cpp:程序的入口文件,主要用于启动文本编辑器的初始化、运行和销毁。

main()

  • 程序启动时首先调用 initTextEditor() 函数初始化文本编辑器。

  • 接着调用 runTextEditor() 启动文本编辑器的主循环,允许用户进行各种操作(如文件操作、文本编辑等)。

  • 最后通过 destroyTextEditor() 销毁编辑器,释放资源。

cpp 复制代码
#include "Editor.h"

int main() {
	// 初始化文本编辑器
	initTextEditor();

	// 运行文本编辑器
	runTextEditor();

	// 销毁文本编辑器
	destroyTextEditor();
	return 0;
}
相关推荐
2301_817031651 小时前
C语言-- 深入理解指针(4)
c语言·开发语言·算法
盐烟3 小时前
C语言-函数练习1
c语言·开发语言·笔记
simple_whu3 小时前
Visual Studio C/C++编译器cl.exe的/source-charset与/execution-charset设置项
c语言·c++·visual studio
苏克贝塔3 小时前
2025 VSCode中如何进行dotnet开发环境配置完整教程
ide·vscode·编辑器
敲代码的瓦龙5 小时前
C++?动态内存管理!!!
c语言·开发语言·数据结构·c++·后端
序属秋秋秋6 小时前
《数据结构初阶》【顺序表 + 单链表 + 双向链表】
c语言·数据结构·笔记·链表
草莓熊Lotso6 小时前
【C语言操作符详解(一)】--进制转换,原反补码,移位操作符,位操作符,逗号表达式,下标访问及函数调用操作符
c语言·经验分享·笔记
猫猫头有亿点炸6 小时前
C语言大写转小写2.0
c语言·开发语言
ykjhr_3d7 小时前
展销编辑器在未来的发展前景
编辑器