305 lines
9.0 KiB
Python
305 lines
9.0 KiB
Python
|
|
# -*- encoding: utf-8 -*-
|
||
|
|
|
||
|
|
import os
|
||
|
|
import sys
|
||
|
|
import re
|
||
|
|
import time
|
||
|
|
import yaml
|
||
|
|
import webbrowser
|
||
|
|
|
||
|
|
from PyQt6.QtCore import Qt
|
||
|
|
from PyQt6.QtWidgets import (
|
||
|
|
QMainWindow, QFileDialog,
|
||
|
|
QMessageBox,
|
||
|
|
)
|
||
|
|
from PyQt6.QtGui import (
|
||
|
|
QIcon, QAction, QFont,
|
||
|
|
)
|
||
|
|
|
||
|
|
import color
|
||
|
|
import config
|
||
|
|
from widget_display import widget_display
|
||
|
|
from widget_tree import widget_tree
|
||
|
|
from widget_interact import widget_interact
|
||
|
|
from widget_term import widget_term
|
||
|
|
import formatter
|
||
|
|
import sheet
|
||
|
|
|
||
|
|
font = QFont("consolas", 10)
|
||
|
|
font.setFamilies(["consolas", "黑体"])
|
||
|
|
|
||
|
|
class message_about(QMessageBox):
|
||
|
|
def __init__(self, window, *arg):
|
||
|
|
super(message_about, self).__init__(*arg)
|
||
|
|
self.window = window
|
||
|
|
self.setIcon(QMessageBox.Icon.Information)
|
||
|
|
self.setText("关于")
|
||
|
|
self.setInformativeText("by 李隆坤\n nugnikoll@qq.com")
|
||
|
|
self.setWindowTitle("关于")
|
||
|
|
# self.setDetailedText("The details are as follows:")
|
||
|
|
self.setStandardButtons(QMessageBox.StandardButton.Ok)
|
||
|
|
|
||
|
|
class main_window(QMainWindow):
|
||
|
|
def __init__(self, ipshell, *arg):
|
||
|
|
super(main_window, self).__init__(*arg)
|
||
|
|
self.ipshell = ipshell
|
||
|
|
self.stdout_save = sys.stdout
|
||
|
|
self.stderr_save = sys.stderr
|
||
|
|
|
||
|
|
sheet.window = self
|
||
|
|
self.ipshell.user_ns["formatter"] = formatter
|
||
|
|
self.ipshell.user_ns["sheet"] = sheet.sheet
|
||
|
|
self.ipshell.user_ns["cell"] = sheet.cell
|
||
|
|
self.ipshell.user_ns["graph"] = sheet.graph
|
||
|
|
self.ipshell.user_ns["plot"] = sheet.plot
|
||
|
|
self.ipshell.user_ns["param"] = sheet.param
|
||
|
|
|
||
|
|
self.setFont(font)
|
||
|
|
self.setWindowTitle("数据处理")
|
||
|
|
self.statusBar().showMessage("就绪")
|
||
|
|
|
||
|
|
self.create_menubar()
|
||
|
|
|
||
|
|
self.widget_display = widget_display(self)
|
||
|
|
self.widget_display.setObjectName("widget display")
|
||
|
|
self.setCentralWidget(self.widget_display)
|
||
|
|
self.widget_tree = widget_tree(self)
|
||
|
|
self.widget_tree.setObjectName("widget tree")
|
||
|
|
self.addDockWidget(Qt.DockWidgetArea.LeftDockWidgetArea, self.widget_tree)
|
||
|
|
self.widget_interact = widget_interact(self)
|
||
|
|
self.widget_interact.setObjectName("widget interact")
|
||
|
|
self.addDockWidget(Qt.DockWidgetArea.RightDockWidgetArea, self.widget_interact)
|
||
|
|
self.widget_term = widget_term(self)
|
||
|
|
self.widget_term.setObjectName("widget term")
|
||
|
|
self.addDockWidget(Qt.DockWidgetArea.BottomDockWidgetArea, self.widget_term)
|
||
|
|
|
||
|
|
self.widget_display.initial_table()
|
||
|
|
|
||
|
|
if "main_window" in config.config:
|
||
|
|
if "geometry" in config.config["main_window"]:
|
||
|
|
self.restoreGeometry(config.config["main_window"]["geometry"])
|
||
|
|
if "state" in config.config["main_window"]:
|
||
|
|
self.restoreState(config.config["main_window"]["state"])
|
||
|
|
|
||
|
|
#redirect iostream
|
||
|
|
def redirect(self):
|
||
|
|
self.stdout_save = sys.stdout
|
||
|
|
self.stderr_save = sys.stderr
|
||
|
|
|
||
|
|
sys.stdout = self.widget_term.text_term
|
||
|
|
sys.stderr = self.widget_term.text_term
|
||
|
|
|
||
|
|
def restore_redirect(self):
|
||
|
|
sys.stdout = self.stdout_save
|
||
|
|
sys.stderr = self.stderr_save
|
||
|
|
|
||
|
|
def create_menubar(self):
|
||
|
|
menu_file = self.menuBar().addMenu("文件(&F)")
|
||
|
|
# act_new_game = QAction(
|
||
|
|
# "新文件(&N)", self, shortcut = "Ctrl+N",
|
||
|
|
# statusTip = "创建一个新文件", triggered = self.new_file
|
||
|
|
# )
|
||
|
|
# menu_file.addAction(act_new_game)
|
||
|
|
act_import_data = QAction(
|
||
|
|
"导入数据(&I)", self, shortcut = "Ctrl+I",
|
||
|
|
statusTip = "从文件导入数据到表格", triggered = self.on_import_data
|
||
|
|
)
|
||
|
|
menu_file.addAction(act_import_data)
|
||
|
|
act_export_data = QAction(
|
||
|
|
"导出数据(&E)", self, shortcut = "Ctrl+T",
|
||
|
|
statusTip = "将表格数据导出到文件", triggered = self.on_export_data
|
||
|
|
)
|
||
|
|
menu_file.addAction(act_export_data)
|
||
|
|
act_reset_data = QAction(
|
||
|
|
"重置数据(&R)", self, #shortcut = "Ctrl+R",
|
||
|
|
statusTip = "将表格数据重置为初始数据", triggered = self.on_reset_data
|
||
|
|
)
|
||
|
|
menu_file.addAction(act_reset_data)
|
||
|
|
act_load_script = QAction(
|
||
|
|
"运行脚本(&L)", self, shortcut = "Ctrl+L",
|
||
|
|
statusTip = "加载并运行一个Python脚本", triggered = self.on_load_script
|
||
|
|
)
|
||
|
|
menu_file.addAction(act_load_script)
|
||
|
|
act_quit = QAction(
|
||
|
|
"退出(&Q)", self, shortcut = "Alt+F4",
|
||
|
|
statusTip = "退出程序", triggered = self.close
|
||
|
|
)
|
||
|
|
menu_file.addAction(act_quit)
|
||
|
|
|
||
|
|
# menu_edit = self.menuBar().addMenu("编辑(&E)")
|
||
|
|
# act_undo = QAction(
|
||
|
|
# "撤销(&U)", self, shortcut = "Ctrl+Z",
|
||
|
|
# statusTip = "撤销最新修改", triggered = self.undo
|
||
|
|
# )
|
||
|
|
# menu_edit.addAction(act_undo)
|
||
|
|
# act_redo = QAction(
|
||
|
|
# "重做(&R)", self, shortcut = "Ctrl+Y",
|
||
|
|
# statusTip = "恢复最新修改", triggered = self.redo
|
||
|
|
# )
|
||
|
|
# menu_edit.addAction(act_redo)
|
||
|
|
|
||
|
|
menu_window = self.menuBar().addMenu("窗口(&W)")
|
||
|
|
act_widget_tree = QAction(
|
||
|
|
"项目窗口(&P)", self,
|
||
|
|
statusTip = "显示/隐藏 项目窗口", triggered = self.toggle_widget_tree
|
||
|
|
)
|
||
|
|
menu_window.addAction(act_widget_tree)
|
||
|
|
act_widget_interact = QAction(
|
||
|
|
"交互窗口(&I)", self,
|
||
|
|
statusTip = "显示/隐藏 交互窗口", triggered = self.toggle_widget_interact
|
||
|
|
)
|
||
|
|
menu_window.addAction(act_widget_interact)
|
||
|
|
act_widget_term = QAction(
|
||
|
|
"终端窗口(&T)", self,
|
||
|
|
statusTip = "显示/隐藏 终端窗口", triggered = self.toggle_widget_term
|
||
|
|
)
|
||
|
|
menu_window.addAction(act_widget_term)
|
||
|
|
|
||
|
|
menu_help = self.menuBar().addMenu("帮助(&H)")
|
||
|
|
act_doc_develop = QAction(
|
||
|
|
"开发指南(&D)", self, shortcut = "F1",
|
||
|
|
statusTip = "在浏览器中阅读开发指南", triggered = self.show_doc_develop
|
||
|
|
)
|
||
|
|
menu_help.addAction(act_doc_develop)
|
||
|
|
act_about = QAction(
|
||
|
|
"关于(&A)", self,
|
||
|
|
statusTip = "关于数据处理", triggered = self.show_about
|
||
|
|
)
|
||
|
|
menu_help.addAction(act_about)
|
||
|
|
|
||
|
|
def on_import_data(self, filename = None):
|
||
|
|
if self.widget_display.get_current_widget() is None:
|
||
|
|
return
|
||
|
|
if not filename:
|
||
|
|
if hasattr(self.on_import_data, "filename"):
|
||
|
|
path_hint = os.path.dirname(self.on_import_data.filename)
|
||
|
|
else:
|
||
|
|
path_hint = ""
|
||
|
|
filename, _ = QFileDialog.getOpenFileName(self, "从文件导入数据", path_hint, "CSV文件(*.csv)")
|
||
|
|
if not filename:
|
||
|
|
return
|
||
|
|
|
||
|
|
if len(filename) < 4 or filename[-4:] != ".csv":
|
||
|
|
filename += ".csv"
|
||
|
|
|
||
|
|
filename = filename.replace("\\", "\\\\")
|
||
|
|
filename = filename.replace("\"", "\\\"")
|
||
|
|
|
||
|
|
widget_id = self.widget_display.get_current_id()
|
||
|
|
self.process(f"sheet(\"{widget_id}\").load_file(\"{filename}\")")
|
||
|
|
|
||
|
|
def on_export_data(self, filename = None):
|
||
|
|
if self.widget_display.get_current_widget() is None:
|
||
|
|
return
|
||
|
|
if not filename:
|
||
|
|
if hasattr(self.on_export_data, "filename"):
|
||
|
|
path_hint = os.path.dirname(self.on_export_data.filename)
|
||
|
|
else:
|
||
|
|
path_hint = ""
|
||
|
|
filename, _ = QFileDialog.getSaveFileName(self, "导出数据到文件", path_hint, "CSV文件(*.csv)")
|
||
|
|
if not filename:
|
||
|
|
return
|
||
|
|
|
||
|
|
if len(filename) < 4 or filename[-4:] != ".csv":
|
||
|
|
filename += ".csv"
|
||
|
|
|
||
|
|
filename = filename.replace("\\", "\\\\")
|
||
|
|
filename = filename.replace("\"", "\\\"")
|
||
|
|
|
||
|
|
widget_id = self.widget_display.get_current_id()
|
||
|
|
self.process(f"sheet(\"{widget_id}\").dump_file(\"{filename}\")")
|
||
|
|
|
||
|
|
def on_reset_data(self):
|
||
|
|
widget_id = self.widget_display.get_current_id()
|
||
|
|
self.process(f"sheet(\"{widget_id}\").reset_content()")
|
||
|
|
|
||
|
|
def get_config(self):
|
||
|
|
data = {
|
||
|
|
"main_window": {
|
||
|
|
"geometry": self.saveGeometry().data(),
|
||
|
|
"state": self.saveState().data(),
|
||
|
|
#"widget_interact": self.widget_interact.get_config(),
|
||
|
|
},
|
||
|
|
}
|
||
|
|
return data
|
||
|
|
|
||
|
|
def closeEvent(self, event):
|
||
|
|
self.restore_redirect()
|
||
|
|
|
||
|
|
data = self.get_config()
|
||
|
|
with open(config.config_file, "w", encoding = "utf-8") as fobj:
|
||
|
|
yaml.safe_dump(data, fobj, allow_unicode = True)
|
||
|
|
|
||
|
|
super().closeEvent(event)
|
||
|
|
|
||
|
|
#process command
|
||
|
|
def process(self, s):
|
||
|
|
s = s.replace("\u2029", "\n")
|
||
|
|
|
||
|
|
print(f"{color.light_green}>>{color.nc}" + s)
|
||
|
|
|
||
|
|
if not s:
|
||
|
|
return
|
||
|
|
if s[0] == "%":
|
||
|
|
result = self.ipshell.magic(s)
|
||
|
|
if result:
|
||
|
|
print(result)
|
||
|
|
else:
|
||
|
|
query = re.search(r"[_0-9a-zA-Z.]+\?", s)
|
||
|
|
if query:
|
||
|
|
result = self.ipshell.magic("pinfo " + query.group()[:-1])
|
||
|
|
else:
|
||
|
|
query = re.search(r"[_0-9a-zA-Z.]+\?\?", s)
|
||
|
|
if query:
|
||
|
|
result = self.ipshell.magic("pinfo2 " + query.group()[:-1])
|
||
|
|
self.ipshell.ex(s)
|
||
|
|
|
||
|
|
#load and execute a script
|
||
|
|
def load_script(self, filename):
|
||
|
|
with open(filename, "r", encoding = "utf-8") as fobj:
|
||
|
|
content = fobj.read()
|
||
|
|
self.ipshell.ex(content)
|
||
|
|
|
||
|
|
#load and execute a script
|
||
|
|
def on_load_script(self, filename = None):
|
||
|
|
if not filename:
|
||
|
|
filename, _ = QFileDialog.getOpenFileName(self, "加载python脚本", "", "Python脚本(*.py)")
|
||
|
|
if not filename:
|
||
|
|
return
|
||
|
|
filename = filename.replace("\\", "\\\\")
|
||
|
|
filename = filename.replace("\"", "\\\"")
|
||
|
|
self.process(f"window.load_script(\"{filename}\")")
|
||
|
|
|
||
|
|
def undo(self):
|
||
|
|
pass
|
||
|
|
|
||
|
|
def redo(self):
|
||
|
|
pass
|
||
|
|
|
||
|
|
def show_doc_develop(self, event):
|
||
|
|
doc_file = os.path.join(config.doc_path, "开发指南.html")
|
||
|
|
webbrowser.open_new_tab(doc_file)
|
||
|
|
|
||
|
|
def show_about(self, event):
|
||
|
|
if not hasattr(self, "msg_about"):
|
||
|
|
self.msg_about = message_about(self)
|
||
|
|
self.msg_about.exec()
|
||
|
|
|
||
|
|
def toggle_widget_interact(self, event):
|
||
|
|
if self.widget_interact.isVisible():
|
||
|
|
self.widget_interact.hide()
|
||
|
|
else:
|
||
|
|
self.widget_interact.show()
|
||
|
|
|
||
|
|
def toggle_widget_tree(self, event):
|
||
|
|
if self.widget_tree.isVisible():
|
||
|
|
self.widget_tree.hide()
|
||
|
|
else:
|
||
|
|
self.widget_tree.show()
|
||
|
|
|
||
|
|
def toggle_widget_term(self, event):
|
||
|
|
if self.widget_term.isVisible():
|
||
|
|
self.widget_term.hide()
|
||
|
|
else:
|
||
|
|
self.widget_term.show()
|