【讨论会】CoCo限制自定义控件后

  用户逐渐减少

限制变多

用户没动力继续写

总的来说,这是为了安全考虑而限制

(未完待续)

import time import cv2 import tkinter as tk from tkinter import filedialog, ttk, messagebox from PIL import Image, ImageTk import numpy as np from sklearn.metrics import confusion_matrix, precision_recall_curve, average_precision_score import matplotlib.pyplot as plt from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg import pandas as pd import subprocess import os import random from threading import Thread # 用于训练时不阻塞界面 class ObjectDetectionApp: def __init__(self): self.window = tk.Tk() self.window.title("YOLOv4训练&验证系统") self.window.geometry("1600x1000") # 模式变量(训练/验证/原评估) self.mode = tk.StringVar(value="validation") self.train_running = False # 训练状态标志 # 模型配置(可自定义) self.base_cfg = "yolov4.cfg" # 基础配置文件 self.custom_cfg = "yolov4-custom.cfg" # 训练用自定义配置 self.weights_path = "yolov4.weights" # 默认权重/训练后权重 self.classes_file = "coco.names" self.classes = self.load_classes() self.COLORS = np.random.uniform(0, 255, size=(len(self.classes), 3)) self.voc_root = None # 新增:VOC数据集根路径 # 训练参数 self.train_params = { "epochs": 100, "batch": 8, "subdivisions": 4, "learning_rate": 0.001, "data_file": "custom.data", "names_file": self.classes_file } # 摄像头初始化 self.cap = None self.image_flipped = True # 评估相关变量(验证阶段使用) self.predicted_labels = [] self.ground_truths = [] self.performance_data = {} # 创建界面 self.create_widgets() self.window.mainloop() def create_widgets(self): # -------------------- 顶部模式栏 -------------------- mode_frame = tk.Frame(self.window, padx=10, pady=5) mode_frame.pack(fill=tk.X) tk.Radiobutton(mode_frame, text="模型训练", variable=self.mode, value="training", command=self.switch_mode).pack(side=tk.LEFT, padx=10) tk.Radiobutton(mode_frame, text="实时验证", variable=self.mode, value="validation", command=self.switch_mode).pack(side=tk.LEFT, padx=10) # -------------------- 训练模块控件 -------------------- self.train_frame = tk.Frame(self.window, padx=10, pady=5) # VOC数据集选择按钮 self.select_voc_btn = tk.Button(self.train_frame, text="选择VOC数据集", command=self.select_voc_dataset) self.select_voc_btn.pack(side=tk.LEFT, padx=10) # 训练参数输入 param_frame = tk.Frame(self.train_frame) tk.Label(param_frame, text="迭代次数:").grid(row=0, column=0, padx=5) self.epochs_entry = ttk.Entry(param_frame, width=8) self.epochs_entry.grid(row=0, column=1, padx=5) self.epochs_entry.insert(0, "100") tk.Label(param_frame, text="批次大小:").grid(row=1, column=0, padx=5) self.batch_entry = ttk.Entry(param_frame, width=8) self.batch_entry.grid(row=1, column=1, padx=5) self.batch_entry.insert(0, "8") param_frame.pack(side=tk.LEFT, padx=10) # 数据集加载按钮 self.load_train_btn = tk.Button(self.train_frame, text="加载训练集", command=self.load_train_dataset) self.load_train_btn.pack(side=tk.LEFT, padx=10) self.load_val_btn = tk.Button(self.train_frame, text="加载验证集", command=self.load_val_dataset) self.load_val_btn.pack(side=tk.LEFT, padx=10) # 开始训练按钮(正确顺序:先定义,再布局) self.start_train_btn = tk.Button(self.train_frame, text="开始训练", command=self.start_training_thread) self.start_train_btn.pack(side=tk.LEFT, padx=10) # -------------------- 主显示区域 -------------------- self.main_frame = tk.Frame(self.window) self.main_frame.pack(fill=tk.BOTH, expand=True) # 视频/图像显示区 self.photo_label = tk.Label(self.main_frame, width=1000, height=600) self.photo_label.pack(side=tk.LEFT, padx=10, pady=10) # 右侧信息区 self.right_frame = tk.Frame(self.main_frame, width=400) self.right_frame.pack(side=tk.RIGHT, padx=10, pady=10, fill=tk.Y) # 日志文本框 self.log_text = tk.Text(self.right_frame, width=40, height=15) self.log_text.pack(pady=5, fill=tk.X) self.log_text.insert(tk.END, "系统日志:\n") # 性能报告区 self.report_canvas = None self.report_frame = tk.Frame(self.right_frame) self.report_frame.pack(pady=5, fill=tk.BOTH, expand=True) # 初始显示验证模式 self.switch_mode() def select_voc_dataset(self): """选择VOC数据集根目录并执行划分""" self.voc_root = filedialog.askdirectory(title="选择VOC数据集根目录(如VOCdevkit/VOC2007)") if not self.voc_root: return # 检查所选目录下是否存在JPEGImages和Annotations目录 jpeg_dir = os.path.join(self.voc_root, "JPEGImages") ann_dir = os.path.join(self.voc_root, "Annotations") if not (os.path.isdir(jpeg_dir) and os.path.isdir(ann_dir)): messagebox.showerror("错误", "所选目录不是正确的VOC数据集根目录(缺少JPEGImages或Annotations目录)") return try: # 执行VOC数据集划分(75:25) self.split_voc_dataset(self.voc_root) self.log_text.insert(tk.END, "数据集划分完成!\n") # 自动设置训练/验证集路径到参数中 self.train_params["train_images"] = os.path.join(self.voc_root, "ImageSets/Main/train.txt") self.train_params["val_images"] = os.path.join(self.voc_root, "ImageSets/Main/val.txt") self.log_text.insert(tk.END, f"训练集路径:{self.train_params['train_images']}\n") self.log_text.insert(tk.END, f"验证集路径:{self.train_params['val_images']}\n") except Exception as e: messagebox.showerror("错误", f"数据集划分失败:{str(e)}") self.log_text.insert(tk.END, f"划分错误:{str(e)}\n") def split_voc_dataset(self, voc_root, train_ratio=0.75): """VOC数据集划分核心逻辑(集成到应用中)""" img_dir = os.path.join(voc_root, "JPEGImages") ann_dir = os.path.join(voc_root, "Annotations") sets_dir = os.path.join(voc_root, "ImageSets", "Main") os.makedirs(sets_dir, exist_ok=True) img_files = [f for f in os.listdir(img_dir) if f.lower().endswith((".jpg", ".jpeg", ".png"))] valid_ids = [] for img_file in img_files: img_id = os.path.splitext(img_file)[0] ann_path = os.path.join(ann_dir, f"{img_id}.xml") if os.path.isfile(ann_path): valid_ids.append(img_file) # 保存带扩展名的图像文件名 if not valid_ids: messagebox.showerror("错误", "未找到任何有效标注文件,确保JPEGImages和Annotations目录中的文件一一对应(除扩展名外文件名相同)") return random.shuffle(valid_ids) total = len(valid_ids) train_count = int(total * train_ratio) train_ids = valid_ids[:train_count] val_ids = valid_ids[train_count:] train_txt = os.path.join(sets_dir, "train.txt") val_txt = os.path.join(sets_dir, "val.txt") # 写入完整路径(如:D:/pycharm/pythonProject/VOCdevkit/VOC2007/JPEGImages/1.jpg) with open(train_txt, "w") as f: for img_file in train_ids: img_full_path = os.path.join(voc_root, "JPEGImages", img_file) f.write(img_full_path + '\n') with open(val_txt, "w") as f: for img_file in val_ids: img_full_path = os.path.join(voc_root, "JPEGImages", img_file) f.write(img_full_path + '\n') self.log_text.insert(tk.END, f"总样本数: {total},训练集: {len(train_ids)},验证集: {len(val_ids)}\n") def switch_mode(self): """模式切换逻辑""" current_mode = self.mode.get() # 关闭摄像头 if self.cap: self.cap.release() self.cap = None self.photo_label.config(image=None) # 清空界面 self.log_text.delete(1.0, tk.END) self.log_text.insert(tk.END, "系统日志:\n") if current_mode == "training": self.train_frame.pack(fill=tk.X, pady=5) self.report_frame.pack_forget() self.log_text.insert(tk.END, "切换到训练模式\n") else: # 验证模式 self.train_frame.pack_forget() self.report_frame.pack(fill=tk.BOTH, expand=True) self.cap = cv2.VideoCapture(0) # 打开摄像头 self.update_validation_frame() # 启动实时验证 self.log_text.insert(tk.END, "切换到实时验证模式\n") def load_train_dataset(self): """加载训练数据集(图像+标注)""" img_dir = filedialog.askdirectory(title="选择训练图像文件夹") label_dir = filedialog.askdirectory(title="选择训练标注文件夹") if img_dir and label_dir: self.train_params["train_images"] = img_dir self.train_params["train_labels"] = label_dir self.log_text.insert(tk.END, f"训练集加载完成:{img_dir}\n") def load_val_dataset(self): """加载验证数据集""" img_dir = filedialog.askdirectory(title="选择验证图像文件夹") label_dir = filedialog.askdirectory(title="选择验证标注文件夹") if img_dir and label_dir: self.train_params["val_images"] = img_dir self.train_params["val_labels"] = label_dir self.log_text.insert(tk.END, f"验证集加载完成:{img_dir}\n") def start_training_thread(self): """启动训练线程(防止界面阻塞)""" if self.train_running: messagebox.showwarning("提示", "训练已在进行中") return self.train_running = True Thread(target=self.start_training, daemon=True).start() def start_training(self): """执行训练流程(调用Darknet命令)""" try: # 生成训练配置文件 self.generate_training_config() # 训练命令(示例,根据实际Darknet路径调整) cmd = [ "darknet.exe", "detector", "train", self.train_params["data_file"], self.custom_cfg, "yolov4.conv.137", # 预训练权重 "-map", # 计算mAP "-gpus", "0", "-batch", self.batch_entry.get(), "-subdivisions", "4", "-epochs", self.epochs_entry.get() ] self.log_text.insert(tk.END, "开始训练...\n") process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True) # 实时输出训练日志 for line in process.stdout: self.log_text.insert(tk.END, line) self.log_text.see(tk.END) # 自动滚动 process.wait() self.train_running = False self.log_text.insert(tk.END, "训练完成!最佳权重已保存\n") # 训练完成后生成性能报告 self.generate_training_report() except Exception as e: self.log_text.insert(tk.END, f"训练错误:{str(e)}\n") self.train_running = False def generate_training_config(self): """生成自定义训练配置文件""" with open(self.base_cfg, 'r') as f: cfg_lines = f.readlines() # 修改批次和subdivisions for i, line in enumerate(cfg_lines): if line.startswith("batch="): cfg_lines[i] = f"batch={self.batch_entry.get()}\n" if line.startswith("subdivisions="): cfg_lines[i] = "subdivisions=4\n" with open(self.custom_cfg, 'w') as f: f.writelines(cfg_lines) # 生成data文件,指定训练和验证的图像列表路径 data_content = f""" train = {os.path.join(self.voc_root, "ImageSets/Main/train.txt")} # 训练集图像列表 valid = {os.path.join(self.voc_root, "ImageSets/Main/val.txt")} # 验证集图像列表 names = {self.classes_file} backup = backup/ eval = coco """ with open(self.train_params["data_file"], 'w') as f: f.write(data_content) def generate_training_report(self): """生成训练性能报告""" # 假设从训练日志或结果文件中读取数据 # 这里模拟加载验证集结果 y_true = ["car", "person", "car", "bike"] y_pred = ["car", "person", "bike", "bike"] confidences = [0.92, 0.85, 0.78, 0.91] # 计算指标 cm = confusion_matrix(y_true, y_pred, labels=self.classes) precision, recall, _ = precision_recall_curve(y_true, confidences, pos_label="car") ap = average_precision_score([1 if x == "car" else 0 for x in y_true], confidences) # 绘制报告 fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 5)) # 混淆矩阵 ax1.imshow(cm, cmap=plt.cm.Blues) ax1.set_title("验证集混淆矩阵") ax1.set_xticks(range(len(self.classes))) ax1.set_xticklabels(self.classes, rotation=45) ax1.set_yticks(range(len(self.classes))) ax1.set_yticklabels(self.classes) # PR曲线 ax2.plot(recall, precision) ax2.set_title(f"PR曲线 (AP={ap:.2f})") ax2.set_xlabel("召回率") ax2.set_ylabel("精确率") # 在界面显示 if self.report_canvas: self.report_canvas.get_tk_widget().destroy() self.report_canvas = FigureCanvasTkAgg(fig, master=self.report_frame) self.report_canvas.draw() self.report_canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True) def update_validation_frame(self): """实时验证摄像头画面""" if not self.cap or not self.mode.get() == "validation": return ret, frame = self.cap.read() if ret: if self.image_flipped: frame = cv2.flip(frame, 1) # 使用训练后的权重进行检测 frame, results = self.detect_objects(frame) # 显示结果 frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) pil_img = Image.fromarray(frame_rgb) tk_img = ImageTk.PhotoImage(image=pil_img) self.photo_label.config(image=tk_img) self.photo_label.image = tk_img # 记录验证结果(可选) self.record_validation_results(results) self.window.after(30, self.update_validation_frame) def detect_objects(self, frame): """使用当前权重进行目标检测""" # 改为使用实例变量 self.net 存储网络 self.net = cv2.dnn.readNetFromDarknet( self.custom_cfg if self.mode.get() == "training" else self.base_cfg, self.weights_path ) # 使用 self.net 获取层信息 layer_names = self.net.getLayerNames() output_layers = [layer_names[i - 1] for i in self.net.getUnconnectedOutLayers()] # 图像预处理 height, width = frame.shape[:2] blob = cv2.dnn.blobFromImage(frame, 0.00392, (416, 416), (0, 0, 0), True, crop=False) # 前向传播(使用 self.net) self.net.setInput(blob) outs = self.net.forward(output_layers) # 注意:output_layers 已提前计算 # 后续代码保持不变... class_ids = [] confidences = [] boxes = [] for out in outs: for detection in out: scores = detection[5:] class_id = np.argmax(scores) confidence = scores[class_id] if confidence > 0.5: # 置信度阈值 center_x = int(detection[0] * width) center_y = int(detection[1] * height) w = int(detection[2] * width) h = int(detection[3] * height) # 计算边界框坐标 x = int(center_x - w / 2) y = int(center_y - h / 2) boxes.append([x, y, w, h]) confidences.append(float(confidence)) class_ids.append(class_id) # 非极大值抑制 indexes = cv2.dnn.NMSBoxes(boxes, confidences, 0.5, 0.4) results = [] for i in range(len(boxes)): if i in indexes: x, y, w, h = boxes[i] label = str(self.classes[class_ids[i]]) confidence = confidences[i] results.append((label, x, y, x + w, y + h, confidence)) # 绘制边界框和标签 color = self.COLORS[class_ids[i]] cv2.rectangle(frame, (x, y), (x + w, y + h), color, 2) cv2.putText(frame, f"{label}: {confidence:.2f}", (x, y - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, color, 2) return frame, results def record_validation_results(self, results): """记录验证结果(预测标签和置信度)""" # 从results中提取预测的标签(假设results格式为:(label, x1, y1, x2, y2, confidence)) for result in results: predicted_label = result[0] # 标签 confidence = result[5] # 置信度 self.predicted_labels.append(predicted_label) # 如果有真实标签(例如从验证集标注文件读取),可添加类似逻辑: # self.ground_truths.append(ground_truth_label) def load_classes(self): with open(self.classes_file, "r") as f: return [line.strip() for line in f.readlines()] def close_app(self): if self.cap: self.cap.release() self.window.destroy() if __name__ == "__main__": app = ObjectDetectionApp() 代码检测
05-14
void wizard::WsfEditor::SetTextFormat(size_t aStart, size_t aCount, const QTextCharFormat& aFmt) { // 将字节位置转换为Qt字符位置以正确处理中文字符 size_t qtStart = BytePosToQtPos(aStart); size_t qtEnd = BytePosToQtPos(aStart + aCount); size_t qtCount = qtEnd - qtStart; size_t end = std::min(qtStart + qtCount, mCurrentStylePos + mCurrentStyleLength); if (end >= mCurrentStylePos) { for (size_t i = std::max(mCurrentStylePos, qtStart) - mCurrentStylePos; i < end - mCurrentStylePos; ++i) { mFormatChanges[ut::cast_to_int(i)] = aFmt; } } } void wizard::WsfEditor::MergeTextFormat(size_t aStart, size_t aCount, const QTextCharFormat& aFmt) { // 将字节位置转换为Qt字符位置以正确处理中文字符 size_t qtStart = BytePosToQtPos(aStart); size_t qtEnd = BytePosToQtPos(aStart + aCount); size_t qtCount = qtEnd - qtStart; size_t end = std::min(qtStart + qtCount, mCurrentStylePos + mCurrentStyleLength); if (end >= mCurrentStylePos) { for (size_t i = std::max(mCurrentStylePos, qtStart) - mCurrentStylePos; i < end - mCurrentStylePos; ++i) { mFormatChanges[ut::cast_to_int(i)].merge(aFmt); } } } size_t wizard::WsfEditor::BytePosToQtPos(size_t aBytePos) { // 如果没有中文字符,字节位置和字符位置是相同的 if (aBytePos == 0) return 0; // 获取当前整个文档内容 QString fullText = toPlainText(); // 将文档转换为UTF-8字节流 std::string utf8Text = ToAscii(fullText); // 如果字节位置超出范围,返回文档末尾 if (aBytePos >= utf8Text.size()) { return fullText.length(); } // 使用二分查找找到对应的Qt字符位置 int left = 0, right = fullText.length(); while (left < right) { int mid = (left + right) / 2; // 获取从开始到mid位置的Qt字符串 QString partialText = fullText.left(mid); std::string partialUtf8 = ToAscii(partialText); if (partialUtf8.size() < aBytePos) { left = mid + 1; } else { right = mid; } } return left; }bool wizard::TextSource::ReadSource(bool aAlwaysFullyReload) { bool loaded(false); UtPath::StatData statData; GetFilePath().Stat(statData); if (statData.mStatType == UtPath::cDIRECTORY) { return false; } if (statData.mStatType == UtPath::cFILE) { if (statData.mFileSizeBytes < (size_t)cMAXIMUM_FILE_SIZE) { // After first read, ensure the parser isn't running. if (mLoaded) { GetWorkspace()->WaitForAbortParsing(); } if (UtTextDocument::ReadFile(GetFilePath())) { SetDeleted(false); SetModified(false); bool requiresParse = GetProject()->SourceReloaded(this); if (requiresParse) { GetProject()->TriggerReparse(); } loaded = true; } } else { const QString msg("The file %1 is too large to open. (%2 MB)."); const QString filePath(QString::fromUtf8(mFilePath.GetSystemPath().c_str())); const double sizeMb = (statData.mFileSizeBytes / 1000000.0); QMessageBox::warning(nullptr, "File too large", msg.arg(filePath).arg(sizeMb)); if (mLoaded) { SetModified(false); Unload(); } loaded = false; } mFileSignature.Update(mFilePath); } else if (statData.mStatType == UtPath::cSTAT_ERROR) { Clear(); Insert(0, "\0", 1); // null terminator SetDeleted(true); SetModified(false); loaded = true; } if (!mViews.empty()) { ReloadEditor(*mViews[0]->mEditorPtr, false, true, aAlwaysFullyReload); } if (loaded) { mPendingChanges.clear(); mAppliedChanges.clear(); } mLoaded = loaded; return loaded; }void wizard::TextSource::ReloadEditor(Editor& aEditor, bool aInitialLoad, bool aForceUnmodified, bool aForceReload) { aEditor.BeginNonUserAction(); if (aInitialLoad || aForceReload) { // Copy text to scintilla char* textPtr = GetText().GetPointer(0); aEditor.BeginNonUserAction(); aEditor.document()->setPlainText(QString::fromUtf8(textPtr)); QTextCursor cur = aEditor.textCursor(); cur.setPosition(0); aEditor.setTextCursor(cur); aEditor.EndNonUserAction(); if (aInitialLoad) { aEditor.document()->clearUndoRedoStacks(); aEditor.document()->setModified(false); } } else { // Diff this text document with scintilla's copy. Apply only the changes. std::string oldText = aEditor.ToAscii(aEditor.ToPlainText()); QVector<TextSourceChange> changes; DiffDocuments(oldText.c_str(), GetPointer(), changes); if (!changes.empty()) { int firstChangePos = -1; QTextCursor cur = aEditor.textCursor(); cur.beginEditBlock(); for (int i = 0; i < changes.size(); ++i) { const TextSourceChange& change = changes[i]; if (i == 0) firstChangePos = ut::cast_to_int(change.mPos); ApplyChangeToQt(changes[i], cur); } cur.endEditBlock(); if (!aForceUnmodified) { SetModified(true, true); } // scroll to first change if (firstChangePos != -1) { QTextCursor cur = aEditor.textCursor(); cur.setPosition(firstChangePos); aEditor.setTextCursor(cur); aEditor.centerCursor(); } } if (!mModified) { // aEditor.SendScintilla(SCI_SETSAVEPOINT); } } aEditor.EndNonUserAction(); aEditor.ReloadComplete(); }void wizard::TextSource::SaveAs() { UtPath dir = GetFilePath(); dir.Up(); QFileDialog dlg(nullptr, "Save File As", dir.GetSystemPath().c_str(), "*.*"); dlg.setDefaultSuffix("txt"); dlg.selectFile(GetFileName().c_str()); dlg.setAcceptMode(QFileDialog::AcceptSave); dlg.setFileMode(QFileDialog::AnyFile); if (dlg.exec()) { QStringList selFiles = dlg.selectedFiles(); if (selFiles.length() == 1) { // Ensure the source is ready WaitForSourceModifications(); std::string newFilePath = selFiles[0].toUtf8().toStdString(); std::ofstream outfile(newFilePath.c_str(), std::ios::binary); while (!outfile) { std::stringstream ss; ss << "Could not save file " + newFilePath + ", check that the file/directory is writable."; int bn = QMessageBox::warning(nullptr, "Could not save", ss.str().c_str(), "Retry", "Cancel"); if (bn != 0) { break; } } if (outfile) { WriteFile(outfile); outfile.close(); UtPath newFile(newFilePath); if (newFile.Stat() == UtPath::cFILE) { // WIZARD_TODO: Open the file for the user? // GetProject()->AddTransientFile(newFile); } emit TextSourceSignals::GetInstance().requestBackup(); } } } } void wizard::TextSource::CopyPathToClipboard() { QApplication::clipboard()->setText(QString::fromUtf8(GetSystemPath().c_str())); }void wizard::TextSource::ApplyChangeToQt(const TextSourceChange& aChange, QTextCursor& aCursor) { if (aCursor.document()->toPlainText().size() < static_cast<int>(aChange.mPos)) { // Adding at the end of the document aCursor.movePosition(QTextCursor::End); aCursor.insertText("\n"); } aCursor.setPosition(ut::cast_to_int(aChange.mPos)); aCursor.setPosition(ut::cast_to_int(aChange.mPos + aChange.mCharsRemoved), QTextCursor::KeepAnchor); // 正确处理UTF-8字符串到Qt的转换 aCursor.insertText(QString::fromUtf8(aChange.mText.c_str())); } void wizard::TextSource::FindIncludeLocations() { if (GetProject()) { GetProject()->FindIncludes(this); } } void wizard::TextSource::SaveAndStore() { SaveWithNotifications(); emit TextSourceSignals::GetInstance().requestBackup(); } void wizard::TextSource::BrowseContainingFolder() { UtPath path = mFilePath; path.Up(); QString pathStr("file:///"); pathStr += QString::fromUtf8(path.GetSystemPath().c_str()); QDesktopServices::openUrl(QUrl(pathStr)); }// 将Qt字符位置转换为对应的UTF-8字节位置 size_t wizard::Editor::QtPosToUtf8Pos(int aQtPos) { if (aQtPos <= 0) return 0; // 获取从开始到指定位置的文本 QTextCursor cur(document()); cur.setPosition(0); cur.setPosition(aQtPos, QTextCursor::KeepAnchor); QString partialText = cur.selectedText(); // 转换为UTF-8并返回字节长度 std::string utf8Text = ToAscii(partialText); return utf8Text.size(); } // 将UTF-8字节位置转换为对应的Qt字符位置 int wizard::Editor::Utf8PosToQtPos(size_t aUtf8Pos) { if (aUtf8Pos == 0) return 0; QString fullText = toPlainText(); std::string fullUtf8 = ToAscii(fullText); if (aUtf8Pos >= fullUtf8.size()) { return fullText.length(); } // 使用二分查找找到对应的Qt位置 int left = 0, right = fullText.length(); while (left < right) { int mid = (left + right) / 2; size_t midUtf8Pos = QtPosToUtf8Pos(mid); if (midUtf8Pos < aUtf8Pos) { left = mid + 1; } else { right = mid; } } return left; } std::string wizard::Editor::ToAscii(const QString& aText) { // 处理段落分隔符,然后转换为UTF-8 QString processedText = aText; processedText.replace(QChar(0x2029), QChar('\n')); // paragraph separator // 直接使用UTF-8编码保持中文字符 return processedText.toUtf8().toStdString(); } void wizard::Editor::DocContentsChange(int aFrom, int aCharsRemoves, int aCharsAdded) { if (mNonUserAction > 0) { return; } // 简化处理:对于任何变化,都重新同步整个文档内容 // 这避免了复杂的位置转换问题,特别是中英文混合的情况 mSourcePtr->HandleDelete(0, 0x0fffffff); QTextCursor cur(document()); cur.setPosition(0); cur.movePosition(QTextCursor::End, QTextCursor::KeepAnchor); QString newText = cur.selectedText(); // Convert to UTF-8 representation std::string text = ToAscii(newText); // 使用UTF-8字符串的实际字节长度 mSourcePtr->HandleInsert(0, text.size(), text.c_str()); }void wizard::ProjectWorkspace::NewProject() { // Use the location of the last opened project // otherwise use the user's home directory // else use the location of the executable QString newLocationDir = QDir::currentPath(); if (!QDir::current().exists()) { // Absolute path of the user's home directory newLocationDir = QDir::toNativeSeparators(QDir::homePath()); // Home directory // openLocationDir = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation); // Documents directory if (newLocationDir.isEmpty()) { newLocationDir = QString::fromStdString(UtPath::GetExePath().GetSystemPath()); } } // Get the new project file name QString fileName = QFileDialog::getSaveFileName(nullptr, "New Project", newLocationDir, "AFSIM Project (*.afproj)"); if (!fileName.isEmpty() && // set the working directory to the new project's directory wizRunMgr.SetWorkingDirectoryToProject(fileName)) { // 使用UTF-8编码处理文件名 std::string utf8FileName = fileName.toUtf8().toStdString(); UtPath fileNamePath(utf8FileName); UtPath filePath = fileNamePath; filePath.Up(); Project* projectPtr = new Project(this); projectPtr->SetProjectLocation(filePath.GetSystemPath(), fileNamePath); } }wizard::Runner::Runner(int& argc, char* argv[], const char* aCompanyStr, const char* aCompanyDomainStr, const char* aProductNameStr, const char* aProductVersionStr) { // Get the executable path // Trim the name of the executable mBinDir = UtPath::GetExePath(); mBinDir.Up(); QApplication::setAttribute(Qt::AA_ShareOpenGLContexts); QApplication::setAttribute(Qt::AA_EnableHighDpiScaling); mApp = ut::make_unique<QApplication>(argc, argv); // 设置UTF-8编码支持中文 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) QTextCodec::setCodecForLocale(QTextCodec::codecForName("UTF-8")); #endif mApp->setQuitOnLastWindowClosed(true); mApp->setWindowIcon(QIcon(":icons/wizard_logo_icon.png")); qApp->setOrganizationName(aCompanyStr); qApp->setOrganizationDomain(aCompanyDomainStr); qApp->setApplicationName(aProductNameStr); qApp->setApplicationDisplayName(aProductNameStr); qApp->setApplicationVersion(aProductVersionStr); }void Scanner::Init() { NewTokCb = nullptr; mNewTokenDataPtr = nullptr; EOL = '\n'; eofSym = 0; // ***Declarations Begin maxT = 58; noSym = 58; int i; for (i = 65; i <= 90; ++i) start.set(i, 1); for (i = 95; i <= 95; ++i) start.set(i, 1); for (i = 97; i <= 122; ++i) start.set(i, 1); for (i = 48; i <= 57; ++i) start.set(i, 31); for (i = 34; i <= 34; ++i) start.set(i, 7); start.set(46, 32); start.set(39, 9); start.set(59, 13); start.set(40, 14); start.set(41, 15); start.set(61, 33); start.set(33, 43); start.set(62, 44); start.set(60, 45); start.set(43, 34); start.set(45, 35); start.set(38, 20); start.set(124, 22); start.set(125, 24); start.set(44, 25); start.set(42, 46); start.set(47, 47); start.set(123, 38); start.set(58, 39); start.set(94, 40); start.set(91, 41); start.set(93, 42); start.set(Buffer::EoF, -1); keywords.set("do", 26); keywords.set("if", 27); keywords.set("for", 28); keywords.set("foreach", 29); keywords.set("in", 30); keywords.set("else", 31); keywords.set("while", 32); keywords.set("break", 33); keywords.set("continue", 34); keywords.set("return", 35); keywords.set("null", 36); keywords.set("NULL", 37); keywords.set("true", 38); keywords.set("false", 39); keywords.set("string", 40); keywords.set("int", 41); keywords.set("double", 42); keywords.set("char", 43); keywords.set("bool", 44); keywords.set("global", 45); keywords.set("static", 46); keywords.set("extern", 47); // ***Declarations End tvalLength = 128; tval = new cocochar_t[tvalLength]; // text of current token // COCO_HEAP_BLOCK_SIZE byte heap + pointer to next heap block heap = malloc(COCO_HEAP_BLOCK_SIZE + sizeof(void*)); firstHeap = heap; heapEnd = (void**)(((char*)heap) + COCO_HEAP_BLOCK_SIZE); *heapEnd = nullptr; heapTop = heap; if (sizeof(Token) > COCO_HEAP_BLOCK_SIZE) { ut::log::fatal() << "Too small COCO_HEAP_BLOCK_SIZE."; exit(1); } pos = -1; line = 1; col = 0; charPos = -1; oldEols = 0; NextCh(); #if !COCO_USE_INLINE_BUFFER bool hasUtf8Bom = false; if (ch == 0xEF) { // check optional byte order mark for UTF-8 NextCh(); int ch1 = ch; NextCh(); int ch2 = ch; if (ch1 == 0xBB && ch2 == 0xBF) { hasUtf8Bom = true; } else { // 不是UTF-8 BOM,回退到开始位置 buffer->SetPos(0); pos = -1; line = 1; col = 0; charPos = -1; NextCh(); } } // 默认启用UTF-8支持,无论是否有BOM // 这样可以支持没有BOM的UTF-8文件(包含中文的脚本文件) Buffer* oldBuf = buffer; buffer = new UTF8Buffer(buffer); if (hasUtf8Bom) { col = 0; charPos = -1; NextCh(); // 跳过BOM } else { // 重新开始读取,使用UTF8Buffer buffer->SetPos(0); pos = -1; line = 1; col = 0; charPos = -1; NextCh(); } delete oldBuf; oldBuf = nullptr; #endif pt = tokens = CreateToken(); // first token is a dummy }UsVal UsCtx::StringLiteral(UtScriptLanguage::Token* t) { std::string str(t->val + 1, t->val + t->len - 1); std::string unescapedStr; bool hasSlash = false; // 使用UTF-8安全的字符迭代 for (size_t i = 0; i < str.length(); ++i) { unsigned char c = static_cast<unsigned char>(str[i]); if (!hasSlash) { if (c == '\\') { hasSlash = true; } else { // 检查是否是UTF-8多字节字符的开始 if (c >= 0x80) // UTF-8多字节字符 { // 计算UTF-8字符的字节数 size_t utf8_len = 1; if ((c & 0xE0) == 0xC0) utf8_len = 2; // 110xxxxx else if ((c & 0xF0) == 0xE0) utf8_len = 3; // 1110xxxx else if ((c & 0xF8) == 0xF0) utf8_len = 4; // 11110xxx // 确保不会越界,并复制完整的UTF-8字符 if (i + utf8_len <= str.length()) { unescapedStr.append(str, i, utf8_len); i += utf8_len - 1; // -1因为for循环会++i } else { // 如果UTF-8字符不完整,按原样添加 unescapedStr += c; } } else { // ASCII字符,直接添加 unescapedStr += c; } } } else { switch (c) { case 'a': unescapedStr += '\a'; break; case 'b': unescapedStr += '\b'; break; case 'r': unescapedStr += '\r'; break; case 'f': unescapedStr += '\f'; break; case 'n': unescapedStr += '\n'; break; case 't': unescapedStr += '\t'; break; case 'v': unescapedStr += '\v'; break; case '\'': case '"': case '\\': unescapedStr += c; break; default: // 对于不认识的转义序列,保留反斜杠和字符 unescapedStr += '\\'; unescapedStr += c; break; } hasSlash = false; } } return CreateData(unescapedStr); }{ StatType fileType(cSTAT_ERROR); std::string sysPath = GetSystemPath(); #ifdef _WIN32 // 使用Unicode版本的文件检查 if (UtPathUnicode::FileExists(mPathString)) { fileType = cFILE; } else if (UtPathUnicode::DirectoryExists(mPathString)) { fileType = cDIRECTORY; } #else struct stat buff; if (0 == ::stat(sysPath.c_str(), &buff)) { if ((buff.st_mode & S_IFDIR) != 0) { fileType = cDIRECTORY; } else { fileType = cFILE; } } #endif return fileType; }// // CUI // // The Advanced Framework for Simulation, Integration, and Modeling (AFSIM) // // Copyright 2003-2015 The Boeing Company. All rights reserved. // // The use, dissemination or disclosure of data in this file is subject to // limitation or restriction. See accompanying README and LICENSE for details. // #include "UtPathUnicode.hpp" #include <sys/stat.h> #ifdef _WIN32 #include <io.h> #include <direct.h> #else #include <unistd.h> #include <sys/types.h> #include <dirent.h> #endif #ifdef _WIN32 std::wstring UtPathUnicode::Utf8ToWide(const std::string& aUtf8) { if (aUtf8.empty()) return std::wstring(); int wideSize = MultiByteToWideChar(CP_UTF8, 0, aUtf8.c_str(), -1, nullptr, 0); if (wideSize <= 0) return std::wstring(); std::wstring wide(wideSize - 1, 0); MultiByteToWideChar(CP_UTF8, 0, aUtf8.c_str(), -1, &wide[0], wideSize); return wide; } std::string UtPathUnicode::WideToUtf8(const std::wstring& aWide) { if (aWide.empty()) return std::string(); int utf8Size = WideCharToMultiByte(CP_UTF8, 0, aWide.c_str(), -1, nullptr, 0, nullptr, nullptr); if (utf8Size <= 0) return std::string(); std::string utf8(utf8Size - 1, 0); WideCharToMultiByte(CP_UTF8, 0, aWide.c_str(), -1, &utf8[0], utf8Size, nullptr, nullptr); return utf8; } std::string UtPathUnicode::AnsiToUtf8(const std::string& aAnsi) { if (aAnsi.empty()) return std::string(); // 先转换为宽字符 int wideSize = MultiByteToWideChar(CP_ACP, 0, aAnsi.c_str(), -1, nullptr, 0); if (wideSize <= 0) return aAnsi; std::wstring wide(wideSize - 1, 0); MultiByteToWideChar(CP_ACP, 0, aAnsi.c_str(), -1, &wide[0], wideSize); // 再转换为UTF-8 return WideToUtf8(wide); } #endif std::string UtPathUnicode::ToSystemPath(const std::string& aUtf8Path) { #ifdef _WIN32 // 在Windows上,将UTF-8路径转换为系统路径 // 对于文件系统操作,我们需要使用宽字符API return aUtf8Path; // 保持UTF-8格式,在具体的文件操作中转换 #else // 在Unix系统上,文件系统通常使用UTF-8 return aUtf8Path; #endif } std::string UtPathUnicode::FromSystemPath(const std::string& aSystemPath) { #ifdef _WIN32 // 假设系统路径可能是ANSI编码,转换为UTF-8 return AnsiToUtf8(aSystemPath); #else return aSystemPath; #endif } bool UtPathUnicode::FileExists(const std::string& aUtf8Path) { #ifdef _WIN32 std::wstring widePath = Utf8ToWide(aUtf8Path); DWORD attributes = GetFileAttributesW(widePath.c_str()); return (attributes != INVALID_FILE_ATTRIBUTES) && !(attributes & FILE_ATTRIBUTE_DIRECTORY); #else struct stat buffer; return (stat(aUtf8Path.c_str(), &buffer) == 0) && S_ISREG(buffer.st_mode); #endif } bool UtPathUnicode::DirectoryExists(const std::string& aUtf8Path) { #ifdef _WIN32 std::wstring widePath = Utf8ToWide(aUtf8Path); DWORD attributes = GetFileAttributesW(widePath.c_str()); return (attributes != INVALID_FILE_ATTRIBUTES) && (attributes & FILE_ATTRIBUTE_DIRECTORY); #else struct stat buffer; return (stat(aUtf8Path.c_str(), &buffer) == 0) && S_ISDIR(buffer.st_mode); #endif } bool UtPathUnicode::CreateDirectory(const std::string& aUtf8Path) { #ifdef _WIN32 std::wstring widePath = Utf8ToWide(aUtf8Path); return CreateDirectoryW(widePath.c_str(), nullptr) != 0; #else return mkdir(aUtf8Path.c_str(), 0755) == 0; #endif } bool UtPathUnicode::DeleteFile(const std::string& aUtf8Path) { #ifdef _WIN32 std::wstring widePath = Utf8ToWide(aUtf8Path); return DeleteFileW(widePath.c_str()) != 0; #else return unlink(aUtf8Path.c_str()) == 0; #endif } size_t UtPathUnicode::GetFileSize(const std::string& aUtf8Path) { #ifdef _WIN32 std::wstring widePath = Utf8ToWide(aUtf8Path); WIN32_FILE_ATTRIBUTE_DATA fileInfo; if (GetFileAttributesExW(widePath.c_str(), GetFileExInfoStandard, &fileInfo)) { LARGE_INTEGER size; size.HighPart = fileInfo.nFileSizeHigh; size.LowPart = fileInfo.nFileSizeLow; return static_cast<size_t>(size.QuadPart); } return 0; #else struct stat buffer; if (stat(aUtf8Path.c_str(), &buffer) == 0) { return static_cast<size_t>(buffer.st_size); } return 0; #endif } 这些代码是为了在QPlainTextEdit中支持中文进行的所有修改,但是目前高亮和自动补全有问题,提供的文件是高亮和自动补全的cpp实现,请协助我修改代码,给出每个需要修改的函数的完整实现,不要省略不需要修改的代码
07-14
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值