Compare commits

...

12 Commits
v1.1 ... main

6 changed files with 286 additions and 42 deletions

View File

@ -2,6 +2,11 @@
这个是一个简约的文本文件编辑工具主要用于编辑以制表符分隔的数据文本文件。与使用Excel编辑相比该程序可以确保文本文件的格式和内容不会发生改变提供更可靠的编辑功能。它简约直观的界面使用户能够轻松地加载、编辑和保存这类文本文件。 这个是一个简约的文本文件编辑工具主要用于编辑以制表符分隔的数据文本文件。与使用Excel编辑相比该程序可以确保文本文件的格式和内容不会发生改变提供更可靠的编辑功能。它简约直观的界面使用户能够轻松地加载、编辑和保存这类文本文件。
## 运行截图
![运行截图](./screenshot/def5f9e083c1f55132797db88b8668f.png)
## TODO ## TODO
- 自动适配文本文件本身的字符编码格式 - 自动适配文本文件本身的字符编码格式
@ -23,10 +28,13 @@
## Qt程序打包 ## Qt程序打包
使用Qt命令行程序Qt 5.14.2 (MinGW 7.3.0 64-bit) 1. 使用Qt命令行程序Qt 5.14.2 (MinGW 7.3.0 64-bit)
进入Qt程序的build目录下的exe所在路径输入 进入Qt程序的build目录下的exe所在路径输入
```shell ```shell
windeployqt TabEditor.exe windeployqt TabEditor.exe
``` ```
2. 使用此项目中的 [packaging.bat](https://begin2019.com:3000/Crimson/TabEditor-Qt/src/branch/main/packaging.bat) ,双击运行 根据提示操作即可。
> 前提安装了Qt 5.14.2 (MinGW 7.3.0 64-bit)

View File

@ -12,6 +12,7 @@
#include <QInputDialog> #include <QInputDialog>
#include <QSqlRecord> #include <QSqlRecord>
#include <QtDebug> #include <QtDebug>
#include <QContextMenuEvent>
MainWindow::MainWindow(QWidget *parent) MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent) : QMainWindow(parent)
@ -19,84 +20,214 @@ MainWindow::MainWindow(QWidget *parent)
{ {
ui->setupUi(this); ui->setupUi(this);
setAcceptDrops(true); // 允许窗口接受拖放事件 setAcceptDrops(true);
connect(ui->actionOpen, &QAction::triggered, this, &MainWindow::onOpenButtonClicked); connect(ui->actionOpen, &QAction::triggered, this, &MainWindow::onOpenButtonClicked);
connect(ui->actionSave, &QAction::triggered, this, &MainWindow::onSaveButtonClicked); connect(ui->actionSave, &QAction::triggered, this, &MainWindow::onSaveButtonClicked);
connect(ui->actionSaveAs, &QAction::triggered, this, &MainWindow::onSaveAsButtonClicked);
QIcon openIcon = QApplication::style()->standardIcon(QStyle::SP_DialogOpenButton); QIcon openIcon = QApplication::style()->standardIcon(QStyle::SP_DialogOpenButton);
QIcon saveIcon = QApplication::style()->standardIcon(QStyle::SP_DialogSaveButton); QIcon saveIcon = QApplication::style()->standardIcon(QStyle::SP_DialogSaveButton);
QIcon saveAsIcon = QApplication::style()->standardIcon(QStyle::SP_DialogSaveButton);
ui->actionOpen->setIcon(openIcon); ui->actionOpen->setIcon(openIcon);
ui->actionSave->setIcon(saveIcon); ui->actionSave->setIcon(saveIcon);
ui->actionSave->setIcon(saveAsIcon);
ui->actionOpen->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_N)); ui->actionOpen->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_N));
ui->actionSave->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_S)); ui->actionSave->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_S));
// 添加 "Convert" 菜单
QMenu* convertMenu = menuBar()->addMenu(tr("转换(&C)")); QMenu* convertMenu = menuBar()->addMenu(tr("转换(&C)"));
// 添加 "Text to SQLite" 子菜单项,并关联槽函数
QAction* textToSqliteAction = convertMenu->addAction(tr("txt 转 SQLite")); QAction* textToSqliteAction = convertMenu->addAction(tr("txt 转 SQLite"));
connect(textToSqliteAction, &QAction::triggered, this, &MainWindow::convertTextToSqlite); connect(textToSqliteAction, &QAction::triggered, this, &MainWindow::convertTextToSqlite);
// 添加 "SQLite to Text" 子菜单项,并关联槽函数
QAction* sqliteToTextAction = convertMenu->addAction(tr("SQLite 转 txt")); QAction* sqliteToTextAction = convertMenu->addAction(tr("SQLite 转 txt"));
connect(sqliteToTextAction, &QAction::triggered, this, &MainWindow::convertSqliteToText); connect(sqliteToTextAction, &QAction::triggered, this, &MainWindow::convertSqliteToText);
QIcon convertIcon = QApplication::style()->standardIcon(QStyle::SP_ArrowRight); QIcon convertIcon = QApplication::style()->standardIcon(QStyle::SP_ArrowRight);
textToSqliteAction->setIcon(convertIcon); textToSqliteAction->setIcon(convertIcon);
sqliteToTextAction->setIcon(convertIcon); sqliteToTextAction->setIcon(convertIcon);
firstColMenu = new QMenu(this);
QAction* addRowAboveAction = firstColMenu->addAction(tr("↑ 向上添加一行"));
QAction* addRowBelowAction = firstColMenu->addAction(tr("向下添加一行 ↓"));
QAction* delRowAction = firstColMenu->addAction(tr("删除"));
connect(addRowAboveAction, &QAction::triggered, this, &MainWindow::onAddRowAboveActionTriggered);
connect(addRowBelowAction, &QAction::triggered, this, &MainWindow::onAddRowBelowActionTriggered);
connect(delRowAction, &QAction::triggered, this, &MainWindow::onDelRowActionTriggered);
firstRowMenu = new QMenu(this);
QAction* addColAboveAction = firstRowMenu->addAction(tr("← 向左添加一列"));
QAction* addColBelowAction = firstRowMenu->addAction(tr("向右添加一列 →"));
QAction* delColAction = firstRowMenu->addAction(tr("删除"));
connect(addColAboveAction, &QAction::triggered, this, &MainWindow::onAddColAboveActionTriggered);
connect(addColBelowAction, &QAction::triggered, this, &MainWindow::onAddColBelowActionTriggered);
connect(delColAction, &QAction::triggered, this, &MainWindow::onDelColActionTriggered);
} }
MainWindow::~MainWindow() MainWindow::~MainWindow()
{ {
delete ui; delete ui;
} }
void MainWindow::contextMenuEvent(QContextMenuEvent* event)
{
QPoint cellPos = ui->tableWidget->viewport()->mapFromGlobal(event->globalPos());
int currentColumn = ui->tableWidget->horizontalHeader()->logicalIndexAt(cellPos.x());
int currentRow = ui->tableWidget->rowAt(cellPos.y());
if (currentColumn == -1 && currentRow == -1) {
// 没有选中行也没有选中列,不显示菜单
return;
}
if (currentColumn == -1 && currentRow >= 0) {
// 选中了行,显示行菜单
ui->tableWidget->selectRow(currentRow);
firstColMenu->popup(event->globalPos());
} else if (currentColumn >= 0 && currentRow == -1) {
// 选中了列,显示列菜单
ui->tableWidget->selectColumn(currentColumn);
firstRowMenu->popup(event->globalPos());
}
}
void MainWindow::onAddRowAboveActionTriggered()
{
QTableWidget* tableWidget = ui->tableWidget;
int currentRow = tableWidget->currentRow();
if (currentRow >= 0)
{
tableWidget->insertRow(currentRow);
}
else
{
// 如果没有选中行,在末尾追加一行
int newRow = tableWidget->rowCount();
tableWidget->insertRow(newRow);
currentRow = newRow;
}
}
void MainWindow::onAddRowBelowActionTriggered()
{
QTableWidget* tableWidget = ui->tableWidget;
int currentRow = tableWidget->currentRow();
if (currentRow >= 0)
{
tableWidget->insertRow(currentRow + 1); // 在下方插入一行
}
else
{
// 如果没有选中行,在末尾追加一行
int newRow = tableWidget->rowCount();
tableWidget->insertRow(newRow);
}
}
void MainWindow::onAddColAboveActionTriggered()
{
QTableWidget* tableWidget = ui->tableWidget;
int currentColumn = tableWidget->currentColumn();
if (currentColumn >= 0)
{
tableWidget->insertColumn(currentColumn);
}
else
{
// 如果没有选中列,在末尾追加一列
int newColumn = tableWidget->columnCount();
tableWidget->insertColumn(newColumn);
currentColumn = newColumn;
}
}
void MainWindow::onAddColBelowActionTriggered()
{
QTableWidget* tableWidget = ui->tableWidget;
int currentColumn = tableWidget->currentColumn();
if (currentColumn >= 0)
{
tableWidget->insertColumn(currentColumn + 1); // 在右侧插入一列
}
else
{
// 如果没有选中列,在末尾追加一列
int newColumn = tableWidget->columnCount();
tableWidget->insertColumn(newColumn);
}
}
void MainWindow::onDelRowActionTriggered()
{
QTableWidget* tableWidget = ui->tableWidget;
int currentRow = tableWidget->currentRow();
tableWidget->removeRow(currentRow);
}
void MainWindow::onDelColActionTriggered()
{
QTableWidget* tableWidget = ui->tableWidget;
int currentColumn = tableWidget->currentColumn();
tableWidget->removeColumn(currentColumn);
}
void MainWindow::showProgressDialog(const QString& labelText, int minimum, int maximum)
{
progressDialog = new QProgressDialog(labelText, "Cancel", minimum, maximum, this);
progressDialog->setWindowModality(Qt::WindowModal);
progressDialog->setAutoClose(false);
progressDialog->setAutoReset(false);
progressDialog->setValue(minimum);
progressDialog->show();
}
void MainWindow::loadTextFile(const QString& fileName) void MainWindow::loadTextFile(const QString& fileName)
{ {
QFile file(fileName); QFile file(fileName);
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
{ {
// 文件打开失败,处理错误 qDebug() << "Failed to open file:" << fileName;
return; return;
} }
QTextStream in(&file); QTextStream in(&file);
// 获取文件大小并预分配内存 // 自动检测并设置正确的文本编码
qint64 fileSize = file.size(); in.setAutoDetectUnicode(true);
QString textData; in.setCodec("UTF-16LE");
textData.reserve(fileSize);
int tableRow = 0; int tableRow = 0;
int tableCol = 0; int tableCol = 0;
QStringList rows;
// 逐行读取文本数据
while (!in.atEnd()) while (!in.atEnd())
{ {
QString line = in.readLine(); QString line = in.readLine();
textData += line + "\n"; // 保存所有文本数据 if (!line.isEmpty())
tableRow++;
if (tableRow == 2)
{ {
tableCol = line.split("\t").count(); // 获取第二行的列数 rows.append(line);
tableRow++;
if (tableRow == 2)
{
QStringList columnValues = line.split("\t", QString::SkipEmptyParts);
tableCol = columnValues.count();
}
} }
} }
file.close(); file.close();
progressDialog = new QProgressDialog("Loading...", "Cancel", 0, 0, this); showProgressDialog("Converting...", 0, tableRow);
progressDialog->setWindowModality(Qt::WindowModal);
progressDialog->setAutoClose(false);
progressDialog->setAutoReset(false);
progressDialog->setRange(0, tableRow);
progressDialog->setValue(0);
progressDialog->show();
// 将数据显示在表格中 // 将数据显示在表格中
QTableWidget* tableWidget = ui->tableWidget; QTableWidget* tableWidget = ui->tableWidget;
// 设置表格的行数和列数 // 设置表格的行数和列数
tableWidget->setRowCount(tableRow); tableWidget->setRowCount(tableRow);
@ -104,7 +235,6 @@ void MainWindow::loadTextFile(const QString& fileName)
tableWidget->blockSignals(true); // 阻止信号发射 tableWidget->blockSignals(true); // 阻止信号发射
QStringList rows = textData.split("\n");
for (int i = 0; i < tableRow; ++i) for (int i = 0; i < tableRow; ++i)
{ {
QStringList row = rows.at(i).split("\t"); QStringList row = rows.at(i).split("\t");
@ -115,34 +245,87 @@ void MainWindow::loadTextFile(const QString& fileName)
tableWidget->setHorizontalHeaderLabels(row); tableWidget->setHorizontalHeaderLabels(row);
} }
// 补充空白内容,确保行的列数与表格的列数一致
while (row.count() < tableCol)
{
row.append(""); // 添加空白内容
}
for (int j = 0; j < tableCol; ++j) for (int j = 0; j < tableCol; ++j)
{ {
QTableWidgetItem* item = new QTableWidgetItem(row.at(j)); QTableWidgetItem* item = new QTableWidgetItem(row.at(j));
tableWidget->setItem(i, j, item); tableWidget->setItem(i, j, item);
} }
progressDialog->setValue(i); progressDialog->setValue(i);
QApplication::processEvents(); // 处理界面事件,使进度条能够更新 QApplication::processEvents();
if (progressDialog->wasCanceled()) if (progressDialog->wasCanceled())
{ {
tableWidget->clear(); tableWidget->clear();
break; break;
} }
} }
progressDialog->setValue(tableRow);
progressDialog->hide(); progressDialog->hide();
tableWidget->blockSignals(false); // 恢复信号发射 tableWidget->blockSignals(false); // 恢复信号发射
} }
void MainWindow::onOpenButtonClicked() void MainWindow::onOpenButtonClicked()
{ {
QString fileName = QFileDialog::getOpenFileName(this, "Open Text File", "", "Text Files (*.txt)"); openFilePath = QFileDialog::getOpenFileName(this, "Open Text File", "", "Text Files (*.txt)");
if (!fileName.isEmpty()) if (!openFilePath.isEmpty())
{ {
loadTextFile(fileName); loadTextFile(openFilePath);
} }
} }
void MainWindow::onSaveButtonClicked() void MainWindow::onSaveButtonClicked()
{
if (!openFilePath.isEmpty())
{
QFile file(openFilePath);
if (file.open(QIODevice::WriteOnly))
{
// 写入UTF-16带有BOM的标记
QByteArray bom;
bom.append((char)0xFF);
bom.append((char)0xFE);
file.write(bom);
QTextStream out(&file);
out.setCodec("UTF-16LE");//Qt6 use: out.setEncoding(QStringConverter::Utf16LE);
QTableWidget* tableWidget = ui->tableWidget;
int rowCount = tableWidget->rowCount();
int columnCount = tableWidget->columnCount();
for (int i = 0; i < rowCount; ++i)
{
for (int j = 0; j < columnCount; ++j)
{
QTableWidgetItem* item = tableWidget->item(i, j);
if (item != nullptr)
{
QString text = item->text();
out << text;
}
if (j != columnCount - 1)
{
out << "\t"; // 制表符分隔
}
}
out << "\t"; // 最后一列再加一个制表符,以防万一
out << "\r\n"; // 换行,注意使用\r\n表示换行符
}
file.close();
}
}
}
void MainWindow::onSaveAsButtonClicked()
{ {
QString fileName = QFileDialog::getSaveFileName(this, "Save Text File", "", "Text Files (*.txt)"); QString fileName = QFileDialog::getSaveFileName(this, "Save Text File", "", "Text Files (*.txt)");
if (!fileName.isEmpty()) if (!fileName.isEmpty())
@ -179,7 +362,7 @@ void MainWindow::onSaveButtonClicked()
out << "\t"; // 制表符分隔 out << "\t"; // 制表符分隔
} }
} }
out << "\t"; // 最后一列再加一个制表符,以防万一
out << "\r\n"; // 换行,注意使用\r\n表示换行符 out << "\r\n"; // 换行,注意使用\r\n表示换行符
} }
@ -205,10 +388,10 @@ void MainWindow::dropEvent(QDropEvent *event)
QList<QUrl> urlList = mimeData->urls(); QList<QUrl> urlList = mimeData->urls();
if (urlList.length() == 1) if (urlList.length() == 1)
{ {
QString fileName = urlList.first().toLocalFile(); openFilePath = urlList.first().toLocalFile();
if (!fileName.isEmpty()) if (!openFilePath.isEmpty())
{ {
loadTextFile(fileName); loadTextFile(openFilePath);
} }
} }
} }
@ -255,7 +438,6 @@ void MainWindow::convertTextToSqlite()
columnNames = processedLine.split('\t', Qt::SkipEmptyParts); columnNames = processedLine.split('\t', Qt::SkipEmptyParts);
} }
//QTextStream textStream2(&textFile);
QList<QStringList> data; QList<QStringList> data;
textStream.seek(0); textStream.seek(0);
// 读取数据行 // 读取数据行
@ -275,6 +457,8 @@ void MainWindow::convertTextToSqlite()
textFile.close(); textFile.close();
showProgressDialog("Converting...", 0, data.size());
// 打开SQLite数据库 // 打开SQLite数据库
QSqlDatabase database = QSqlDatabase::addDatabase("QSQLITE"); QSqlDatabase database = QSqlDatabase::addDatabase("QSQLITE");
database.setDatabaseName(sqliteFilePath); database.setDatabaseName(sqliteFilePath);
@ -311,6 +495,7 @@ void MainWindow::convertTextToSqlite()
insertQuery.chop(2); // 移除最后的逗号和空格 insertQuery.chop(2); // 移除最后的逗号和空格
insertQuery += ");"; insertQuery += ");";
int progress = 0;
for (const QStringList& row : data) for (const QStringList& row : data)
{ {
if (row.size() != columnNames.size()) if (row.size() != columnNames.size())
@ -332,8 +517,17 @@ void MainWindow::convertTextToSqlite()
database.close(); database.close();
return; return;
} }
++progress;
progressDialog->setValue(progress);
QApplication::processEvents();
if (progressDialog->wasCanceled())
{
break;
}
} }
progressDialog->hide();
database.commit(); database.commit();
database.close(); database.close();
@ -389,6 +583,8 @@ void MainWindow::convertSqliteToText()
return; return;
} }
showProgressDialog("Converting...", 0, query.size());
// Open the text file to write data // Open the text file to write data
QFile textFile(textFilePath); QFile textFile(textFilePath);
if (!textFile.open(QIODevice::WriteOnly)) if (!textFile.open(QIODevice::WriteOnly))
@ -408,6 +604,7 @@ void MainWindow::convertSqliteToText()
textStream.setCodec("UTF-16LE"); textStream.setCodec("UTF-16LE");
// Write the data to the text file // Write the data to the text file
int progress = 0;
while (query.next()) while (query.next())
{ {
QSqlRecord record = query.record(); QSqlRecord record = query.record();
@ -418,9 +615,19 @@ void MainWindow::convertSqliteToText()
if (i < record.count() - 1) if (i < record.count() - 1)
textStream << "\t"; textStream << "\t";
} }
textStream << "\t";
textStream << "\r\n"; textStream << "\r\n";
++progress;
progressDialog->setValue(progress);
QApplication::processEvents();
if (progressDialog->wasCanceled())
{
break;
}
} }
progressDialog->hide();
textFile.close(); textFile.close();
database.close(); database.close();

View File

@ -1,9 +1,8 @@
#ifndef MAINWINDOW_H #ifndef MAINWINDOW_H
#define MAINWINDOW_H #define MAINWINDOW_H
#include "qprogressdialog.h"
#include <QThread>
#include <QMainWindow> #include <QMainWindow>
#include <QProgressDialog>
#include <QTableWidget> #include <QTableWidget>
#include <QDragEnterEvent> #include <QDragEnterEvent>
#include <QMimeData> #include <QMimeData>
@ -25,15 +24,26 @@ public:
void dropEvent(QDropEvent *event); void dropEvent(QDropEvent *event);
void convertTextToSqlite(); void convertTextToSqlite();
void convertSqliteToText(); void convertSqliteToText();
void showProgressDialog(const QString& labelText, int minimum, int maximum);
void contextMenuEvent(QContextMenuEvent* event);
private slots: private slots:
void onOpenButtonClicked(); void onOpenButtonClicked();
void onSaveButtonClicked(); void onSaveButtonClicked();
void onSaveAsButtonClicked();
void onAddRowAboveActionTriggered();
void onAddRowBelowActionTriggered();
void onAddColAboveActionTriggered();
void onAddColBelowActionTriggered();
void onDelRowActionTriggered();
void onDelColActionTriggered();
private: private:
Ui::MainWindow *ui; Ui::MainWindow *ui;
QProgressDialog* progressDialog; // 添加进度对话框指针 QProgressDialog* progressDialog;
QMenu* menuFile; QString openFilePath;
QAction* actionFile; QMenu* firstColMenu; // 右键菜单
QMenu* firstRowMenu; // 右键菜单
}; };
#endif // MAINWINDOW_H #endif // MAINWINDOW_H

View File

@ -20,6 +20,9 @@
<layout class="QVBoxLayout" name="verticalLayout"> <layout class="QVBoxLayout" name="verticalLayout">
<item> <item>
<layout class="QGridLayout" name="gridLayout"> <layout class="QGridLayout" name="gridLayout">
<property name="spacing">
<number>0</number>
</property>
<item row="0" column="0"> <item row="0" column="0">
<widget class="QTableWidget" name="tableWidget"/> <widget class="QTableWidget" name="tableWidget"/>
</item> </item>
@ -42,10 +45,10 @@
</property> </property>
<addaction name="actionOpen"/> <addaction name="actionOpen"/>
<addaction name="actionSave"/> <addaction name="actionSave"/>
<addaction name="actionSaveAs"/>
</widget> </widget>
<addaction name="menuOpen"/> <addaction name="menuOpen"/>
</widget> </widget>
<widget class="QStatusBar" name="statusbar"/>
<action name="actionOpen"> <action name="actionOpen">
<property name="text"> <property name="text">
<string>打开</string> <string>打开</string>
@ -53,12 +56,18 @@
</action> </action>
<action name="actionSave"> <action name="actionSave">
<property name="icon"> <property name="icon">
<iconset theme="Save"/> <iconset theme="Save">
<normaloff>.</normaloff>.</iconset>
</property> </property>
<property name="text"> <property name="text">
<string>保存</string> <string>保存</string>
</property> </property>
</action> </action>
<action name="actionSaveAs">
<property name="text">
<string>另存为...</string>
</property>
</action>
</widget> </widget>
<resources/> <resources/>
<connections/> <connections/>

View File

@ -55,6 +55,14 @@ if "%BUILD_TYPE%"=="1" (
exit /b 1 exit /b 1
) )
REM 寻找当前要编译的exe程序
set "EXECUTABLE=%BUILD_DIR%\TabEditor.exe"
if not exist "%EXECUTABLE%" (
echo Error: Executable not found in the selected build directory.
pause
exit /b 1
)
REM 切换到Qt环境变量脚本所在目录 REM 切换到Qt环境变量脚本所在目录
cd /d "C:\Qt\Qt5.14.2\5.14.2\mingw73_64\bin" cd /d "C:\Qt\Qt5.14.2\5.14.2\mingw73_64\bin"
@ -65,11 +73,13 @@ REM 切换到构建目录
cd /d "%BUILD_DIR%" cd /d "%BUILD_DIR%"
REM 运行windeployqt命令来打包依赖项 REM 运行windeployqt命令来打包依赖项
windeployqt TabEditor.exe echo Running windeployqt to package dependencies...
windeployqt "%EXECUTABLE%"
REM 切换回脚本所在目录 REM 切换回脚本所在目录
cd /d "%SCRIPT_DIR%" cd /d "%SCRIPT_DIR%"
REM 完成打包 REM 完成打包
echo.
echo Packaging completed successfully. echo Packaging completed successfully.
pause pause

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB