main_window.py 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750
  1. import os
  2. import sys
  3. from datetime import datetime
  4. from PyQt5.QtWidgets import (QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
  5. QTabWidget, QLabel, QPushButton, QComboBox,
  6. QStatusBar, QSplitter, QProgressBar, QAction,
  7. QFileDialog, QMessageBox, QMenu, QGroupBox)
  8. from PyQt5.QtCore import Qt, QTimer, pyqtSlot, QUrl, QSize
  9. from PyQt5.QtGui import QIcon, QPixmap, QFont, QColor, QPainter
  10. from PyQt5.QtWebEngineWidgets import QWebEngineView
  11. import psutil
  12. from PyQt5.QtWidgets import QApplication
  13. import time
  14. from ui.components.map_view import MapView
  15. from ui.components.camera_view import CameraView
  16. from ui.components.alert_panel import AlertPanel
  17. from ui.components.control_panel import ControlPanel
  18. from ui.components.statistics_panel import StatisticsPanel
  19. from ui.components.drone_manager import DroneManager
  20. from ui.components.grid_camera_view import GridCameraView
  21. from PyQt5.QtChart import QChartView, QChart, QPieSeries
  22. class MainWindow(QMainWindow):
  23. """
  24. 森林多模态灾害监测系统主窗口
  25. """
  26. def __init__(self, config):
  27. super().__init__()
  28. self.config = config
  29. self.last_fire_update_time = 0 # 添加上次火灾更新时间记录
  30. self.fire_update_interval = 5 # 设置最小更新间隔为5秒
  31. self.last_animal_update = 0 # 添加动物检测更新时间记录
  32. self.min_update_interval = 5 # 设置最小更新间隔为5秒
  33. self.init_ui()
  34. # 连接摄像头视图的火灾检测信号
  35. self.camera_view.fire_detected.connect(self.on_fire_detected)
  36. def init_ui(self):
  37. """初始化UI界面"""
  38. # 设置窗口属性
  39. self.setWindowTitle("森林多模态灾害监测系统")
  40. self.setGeometry(100, 100, 1280, 800)
  41. # 设置图标
  42. icon_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'assets', 'icon.png')
  43. if os.path.exists(icon_path):
  44. self.setWindowIcon(QIcon(icon_path))
  45. # 创建主布局
  46. main_widget = QWidget()
  47. self.setCentralWidget(main_widget)
  48. main_layout = QVBoxLayout(main_widget)
  49. main_layout.setContentsMargins(5, 5, 5, 5)
  50. # 创建顶部工具栏
  51. self.create_toolbar()
  52. # 创建三栏主分割器
  53. main_splitter = QSplitter(Qt.Horizontal)
  54. main_layout.addWidget(main_splitter)
  55. # === 左侧栏:控制面板和地图 ===
  56. left_panel = QWidget()
  57. left_layout = QVBoxLayout(left_panel)
  58. left_layout.setContentsMargins(0, 0, 0, 0)
  59. # 控制面板
  60. self.control_panel = ControlPanel(self.config)
  61. left_layout.addWidget(self.control_panel)
  62. # 地图视图
  63. self.map_view = MapView(self.config)
  64. left_layout.addWidget(self.map_view)
  65. # === 中间栏:摄像头视图和告警面板 ===
  66. middle_panel = QWidget()
  67. middle_layout = QVBoxLayout(middle_panel)
  68. middle_layout.setContentsMargins(0, 0, 0, 0)
  69. # 创建标签页容器
  70. camera_tabs = QTabWidget()
  71. camera_tabs.setTabPosition(QTabWidget.South)
  72. camera_tabs.setStyleSheet("QTabBar::tab { background-color: #102040; color: white; padding: 6px 12px; margin-right: 2px; border-top-left-radius: 4px; border-top-right-radius: 4px; } QTabBar::tab:selected { background-color: #1a3a5a; }")
  73. # 添加九宫格视图标签页
  74. self.grid_camera_view = GridCameraView()
  75. camera_tabs.addTab(self.grid_camera_view, "多路监控")
  76. # 连接九宫格视图的灾害检测信号
  77. self.grid_camera_view.fire_detected.connect(self.on_fire_detected)
  78. self.grid_camera_view.animal_detected.connect(self.on_animal_detected)
  79. # 添加单路摄像头视图标签页
  80. self.camera_view = CameraView(self.config)
  81. camera_tabs.addTab(self.camera_view, "单路监控")
  82. # 添加无人机管理标签页
  83. self.drone_manager = DroneManager(self.config)
  84. camera_tabs.addTab(self.drone_manager, "无人机集群")
  85. # 将标签页容器添加到中间布局
  86. middle_layout.addWidget(camera_tabs, 7) # 占70%高度
  87. # 告警面板
  88. self.alert_panel = AlertPanel(self.config)
  89. middle_layout.addWidget(self.alert_panel, 3) # 占30%高度
  90. # 连接告警面板的信号
  91. self.alert_panel.alert_added.connect(self.on_alert_added)
  92. # 连接告警处理信号
  93. self.alert_panel.alert_processed.connect(self.on_alert_processed)
  94. # === 右侧栏:统计信息面板 ===
  95. right_panel = QWidget()
  96. right_layout = QVBoxLayout(right_panel)
  97. right_layout.setContentsMargins(0, 0, 0, 0)
  98. right_layout.setSpacing(5)
  99. right_panel.setStyleSheet("background-color: #0a1a2a; "
  100. "QGroupBox { background-color: #102040; border: 1px solid #1e3a5a; border-radius: 5px; margin-top: 8px; } "
  101. "QGroupBox::title { subcontrol-origin: margin; left: 10px; padding: 0 3px; color: white; font-weight: bold; }"
  102. "QLabel { color: white; }"
  103. "QComboBox { color: white; background-color: #1a3a5a; border: 1px solid #2a4a6a; }"
  104. "QPushButton { color: white; background-color: #1a3a5a; border: 1px solid #2a4a6a; }")
  105. # 标题标签
  106. stats_title = QLabel("统计信息")
  107. stats_title.setFont(QFont("Microsoft YaHei", 14, QFont.Bold))
  108. stats_title.setStyleSheet("color: white; margin: 5px;")
  109. stats_title.setAlignment(Qt.AlignCenter)
  110. right_layout.addWidget(stats_title)
  111. # 创建统计面板
  112. self.statistics_panel = StatisticsPanel(self.config)
  113. # ==== 概览部分 ====
  114. overview_group = QGroupBox("告警概览")
  115. overview_group.setObjectName("overview_group")
  116. overview_layout = QVBoxLayout(overview_group)
  117. overview_layout.setContentsMargins(5, 10, 5, 10) # 设置合适的边距
  118. # 告警统计数字
  119. stats_layout = QHBoxLayout()
  120. fire_label = QLabel(f"火灾告警: {self.statistics_panel.alert_stats['fire']}")
  121. fire_label.setObjectName("fire_overview")
  122. fire_label.setStyleSheet("color: #ff6666; font-weight: bold;")
  123. animal_label = QLabel(f"动物告警: {self.statistics_panel.alert_stats['animal']}")
  124. animal_label.setObjectName("animal_overview")
  125. animal_label.setStyleSheet("color: #66ff66; font-weight: bold;")
  126. landslide_label = QLabel(f"滑坡告警: {self.statistics_panel.alert_stats['landslide']}")
  127. landslide_label.setObjectName("landslide_overview")
  128. landslide_label.setStyleSheet("color: #6666ff; font-weight: bold;")
  129. pest_label = QLabel(f"病虫害: {self.statistics_panel.alert_stats['pest']}")
  130. pest_label.setObjectName("pest_overview")
  131. pest_label.setStyleSheet("color: #cc99ff; font-weight: bold;")
  132. stats_layout.addWidget(fire_label)
  133. stats_layout.addWidget(animal_label)
  134. stats_layout.addWidget(landslide_label)
  135. stats_layout.addWidget(pest_label)
  136. overview_layout.addLayout(stats_layout)
  137. # 饼图视图
  138. pie_chart = self.statistics_panel.create_pie_chart()
  139. pie_chart.setObjectName("overview_pie_chart")
  140. pie_chart.setMinimumHeight(250) # 设置最小高度
  141. pie_chart.setFixedHeight(250) # 设置固定高度,防止尺寸变化
  142. overview_layout.addWidget(pie_chart)
  143. right_layout.addWidget(overview_group)
  144. # ==== 趋势图部分 ====
  145. trend_group = QGroupBox("告警趋势")
  146. trend_group.setObjectName("trend_group")
  147. trend_layout = QVBoxLayout(trend_group)
  148. trend_layout.setContentsMargins(5, 10, 5, 10) # 设置合适的边距
  149. # 时间范围选择器
  150. time_range_layout = QHBoxLayout()
  151. time_range_layout.addWidget(QLabel("时间范围:"))
  152. time_range_combo = QComboBox()
  153. time_range_combo.setObjectName("time_range_combo")
  154. time_range_combo.addItems(["最近24小时", "最近7天", "最近30天", "本月", "本年"])
  155. time_range_combo.currentIndexChanged.connect(self.statistics_panel.change_time_range)
  156. time_range_layout.addWidget(time_range_combo)
  157. time_range_layout.addStretch(1)
  158. trend_layout.addLayout(time_range_layout)
  159. # 趋势图视图
  160. trend_chart = self.statistics_panel.create_trend_chart_view()
  161. trend_chart.setObjectName("trend_chart_view")
  162. trend_chart.setMinimumHeight(280) # 设置最小高度
  163. trend_chart.setFixedHeight(280) # 设置固定高度,防止尺寸变化
  164. trend_layout.addWidget(trend_chart)
  165. right_layout.addWidget(trend_group)
  166. # ==== 区域统计部分 ====
  167. region_group = QGroupBox("区域统计")
  168. region_group.setObjectName("region_group")
  169. region_layout = QVBoxLayout(region_group)
  170. region_layout.setContentsMargins(5, 10, 5, 10) # 设置合适的边距
  171. # 区域统计图表
  172. region_chart = self.statistics_panel.create_region_chart_view()
  173. region_chart.setObjectName("region_chart_view")
  174. region_chart.setMinimumHeight(280) # 设置最小高度
  175. region_chart.setFixedHeight(280) # 设置固定高度,防止尺寸变化
  176. region_layout.addWidget(region_chart)
  177. right_layout.addWidget(region_group)
  178. # 刷新按钮
  179. refresh_btn = QPushButton("刷新统计信息")
  180. refresh_btn.clicked.connect(self.statistics_panel.refresh_statistics)
  181. right_layout.addWidget(refresh_btn)
  182. # 将三个面板添加到主分割器
  183. main_splitter.addWidget(left_panel)
  184. main_splitter.addWidget(middle_panel)
  185. main_splitter.addWidget(right_panel)
  186. # 设置三栏分割比例
  187. main_splitter.setSizes([int(self.width() * 0.2), int(self.width() * 0.55), int(self.width() * 0.25)])
  188. # 创建状态栏
  189. self.create_statusbar()
  190. # 创建定时器,定期更新UI
  191. self.update_timer = QTimer(self)
  192. self.update_timer.timeout.connect(self.update_ui)
  193. self.update_timer.start(5000) # 每5秒更新一次
  194. # 应用暗色/亮色主题
  195. self.apply_theme()
  196. def create_toolbar(self):
  197. """创建工具栏"""
  198. self.toolbar = self.addToolBar("主工具栏")
  199. self.toolbar.setMovable(False)
  200. # 添加启动监控按钮
  201. start_action = QAction(QIcon(os.path.join(os.path.dirname(os.path.dirname(__file__)), 'assets', 'start.png')), "启动监控", self)
  202. start_action.triggered.connect(self.start_monitoring)
  203. self.toolbar.addAction(start_action)
  204. # 添加停止监控按钮
  205. stop_action = QAction(QIcon(os.path.join(os.path.dirname(os.path.dirname(__file__)), 'assets', 'stop.png')), "停止监控", self)
  206. stop_action.triggered.connect(self.stop_monitoring)
  207. self.toolbar.addAction(stop_action)
  208. self.toolbar.addSeparator()
  209. # 添加无人机控制菜单
  210. drone_menu = QMenu("无人机", self)
  211. # 添加起飞所有无人机动作
  212. takeoff_all_action = QAction(QIcon(os.path.join(os.path.dirname(os.path.dirname(__file__)), 'assets', 'takeoff.png')), "起飞所有无人机", self)
  213. takeoff_all_action.triggered.connect(self.takeoff_all_drones)
  214. drone_menu.addAction(takeoff_all_action)
  215. # 添加返航所有无人机动作
  216. return_all_action = QAction(QIcon(os.path.join(os.path.dirname(os.path.dirname(__file__)), 'assets', 'return.png')), "返航所有无人机", self)
  217. return_all_action.triggered.connect(self.return_all_drones)
  218. drone_menu.addAction(return_all_action)
  219. # 添加紧急停止所有无人机动作
  220. emergency_action = QAction(QIcon(os.path.join(os.path.dirname(os.path.dirname(__file__)), 'assets', 'emergency.png')), "紧急停止所有无人机", self)
  221. emergency_action.triggered.connect(self.emergency_stop_all_drones)
  222. drone_menu.addAction(emergency_action)
  223. # 添加无人机菜单按钮
  224. drone_action = QAction(QIcon(os.path.join(os.path.dirname(os.path.dirname(__file__)), 'assets', 'drone.png')), "无人机控制", self)
  225. drone_action.setMenu(drone_menu)
  226. self.toolbar.addAction(drone_action)
  227. self.toolbar.addSeparator()
  228. # 添加导入数据按钮
  229. import_action = QAction(QIcon(os.path.join(os.path.dirname(os.path.dirname(__file__)), 'assets', 'import.png')), "导入数据", self)
  230. import_action.triggered.connect(self.import_data)
  231. self.toolbar.addAction(import_action)
  232. # 添加导出报告按钮
  233. export_action = QAction(QIcon(os.path.join(os.path.dirname(os.path.dirname(__file__)), 'assets', 'export.png')), "导出报告", self)
  234. export_action.triggered.connect(self.export_report)
  235. self.toolbar.addAction(export_action)
  236. self.toolbar.addSeparator()
  237. # 添加设置按钮
  238. settings_action = QAction(QIcon(os.path.join(os.path.dirname(os.path.dirname(__file__)), 'assets', 'settings.png')), "设置", self)
  239. settings_action.triggered.connect(self.show_settings)
  240. self.toolbar.addAction(settings_action)
  241. # 添加帮助按钮
  242. help_action = QAction(QIcon(os.path.join(os.path.dirname(os.path.dirname(__file__)), 'assets', 'help.png')), "帮助", self)
  243. help_action.triggered.connect(self.show_help)
  244. self.toolbar.addAction(help_action)
  245. def create_statusbar(self):
  246. """创建状态栏"""
  247. self.statusbar = QStatusBar()
  248. self.setStatusBar(self.statusbar)
  249. # 添加系统状态标签
  250. self.status_label = QLabel("系统状态: 待机")
  251. self.statusbar.addWidget(self.status_label)
  252. # 添加CPU使用率进度条
  253. self.cpu_label = QLabel("CPU: ")
  254. self.statusbar.addPermanentWidget(self.cpu_label)
  255. self.cpu_progress = QProgressBar()
  256. self.cpu_progress.setMaximumWidth(100)
  257. self.cpu_progress.setMaximumHeight(16)
  258. self.cpu_progress.setRange(0, 100)
  259. self.cpu_progress.setValue(0)
  260. self.statusbar.addPermanentWidget(self.cpu_progress)
  261. # 添加内存使用率进度条
  262. self.memory_label = QLabel("内存: ")
  263. self.statusbar.addPermanentWidget(self.memory_label)
  264. self.memory_progress = QProgressBar()
  265. self.memory_progress.setMaximumWidth(100)
  266. self.memory_progress.setMaximumHeight(16)
  267. self.memory_progress.setRange(0, 100)
  268. self.memory_progress.setValue(0)
  269. self.statusbar.addPermanentWidget(self.memory_progress)
  270. # 添加时间标签
  271. self.time_label = QLabel()
  272. self.update_time() # 初始化时间
  273. self.statusbar.addPermanentWidget(self.time_label)
  274. # 创建时间更新定时器
  275. self.time_timer = QTimer(self)
  276. self.time_timer.timeout.connect(self.update_time)
  277. self.time_timer.start(1000) # 每秒更新一次
  278. def update_time(self):
  279. """更新状态栏时间"""
  280. current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
  281. self.time_label.setText(current_time)
  282. def apply_theme(self):
  283. """应用主题"""
  284. if self.config.get('dark_mode', True):
  285. # 应用暗色主题
  286. self.setStyleSheet("""
  287. QMainWindow, QWidget {
  288. background-color: #2D2D30;
  289. color: #FFFFFF;
  290. }
  291. QTabWidget::pane {
  292. border: 1px solid #3F3F46;
  293. background-color: #252526;
  294. }
  295. QTabBar::tab {
  296. background-color: #2D2D30;
  297. color: #FFFFFF;
  298. padding: 5px 15px;
  299. border: 1px solid #3F3F46;
  300. }
  301. QTabBar::tab:selected {
  302. background-color: #007ACC;
  303. }
  304. QPushButton {
  305. background-color: #0E639C;
  306. color: white;
  307. border: none;
  308. padding: 5px 15px;
  309. border-radius: 2px;
  310. }
  311. QPushButton:hover {
  312. background-color: #1177BB;
  313. }
  314. QPushButton:pressed {
  315. background-color: #13669C;
  316. }
  317. """)
  318. else:
  319. # 应用亮色主题
  320. self.setStyleSheet("")
  321. def update_ui(self):
  322. """定期更新UI界面"""
  323. # 更新系统状态
  324. self.status_label.setText("系统状态: 正常运行中")
  325. # 获取真实CPU和内存使用率
  326. cpu_usage = psutil.cpu_percent()
  327. memory_usage = psutil.virtual_memory().percent
  328. self.cpu_progress.setValue(cpu_usage)
  329. self.memory_progress.setValue(memory_usage)
  330. # 更新子组件
  331. self.camera_view.update_view()
  332. self.map_view.update_view()
  333. self.alert_panel.update_alerts()
  334. self.statistics_panel.update_statistics()
  335. # 添加无人机组件更新
  336. if hasattr(self, 'drone_manager'):
  337. self.drone_manager.update_drone_display()
  338. # 更新右侧概览面板的统计数据
  339. self.update_overview_stats()
  340. @pyqtSlot()
  341. def start_monitoring(self):
  342. """启动监控"""
  343. self.status_label.setText("系统状态: 监控中")
  344. self.camera_view.start_monitoring()
  345. # 更新其他组件状态
  346. @pyqtSlot()
  347. def stop_monitoring(self):
  348. """停止监控"""
  349. self.status_label.setText("系统状态: 已停止")
  350. self.camera_view.stop_monitoring()
  351. # 更新其他组件状态
  352. @pyqtSlot()
  353. def import_data(self):
  354. """导入数据"""
  355. file_dialog = QFileDialog()
  356. file_dialog.setFileMode(QFileDialog.ExistingFile)
  357. file_dialog.setNameFilter("数据文件 (*.csv *.json *.zip)")
  358. if file_dialog.exec_():
  359. file_paths = file_dialog.selectedFiles()
  360. if file_paths:
  361. # 处理导入文件
  362. QMessageBox.information(self, "导入数据", f"已选择导入文件: {file_paths[0]}")
  363. @pyqtSlot()
  364. def export_report(self):
  365. """导出报告"""
  366. file_dialog = QFileDialog()
  367. file_dialog.setAcceptMode(QFileDialog.AcceptSave)
  368. file_dialog.setNameFilter("报告文件 (*.pdf *.docx *.html)")
  369. if file_dialog.exec_():
  370. file_paths = file_dialog.selectedFiles()
  371. if file_paths:
  372. # 处理导出报告
  373. QMessageBox.information(self, "导出报告", f"报告将保存到: {file_paths[0]}")
  374. @pyqtSlot()
  375. def show_settings(self):
  376. """显示设置对话框"""
  377. self.tab_widget.setCurrentIndex(3) # 切换到设置页面
  378. @pyqtSlot()
  379. def show_help(self):
  380. """显示帮助信息"""
  381. QMessageBox.information(self, "帮助", "森林多模态灾害监测系统\n版本: 1.0.0\n\n基于YOLOv5的智能监测系统,用于森林火灾、滑坡、动物盗猎等多灾害监测。")
  382. @pyqtSlot()
  383. def takeoff_all_drones(self):
  384. """起飞所有无人机"""
  385. if hasattr(self, 'drone_manager'):
  386. # 切换到无人机管理标签页
  387. for i in range(4): # 假设标签页在索引1-3
  388. if "无人机" in self.findChildren(QTabWidget)[i].tabText(1):
  389. self.findChildren(QTabWidget)[i].setCurrentIndex(1)
  390. break
  391. # 修改所有无人机状态为已起飞
  392. for drone_id, drone in self.drone_manager.drones.items():
  393. drone.status = "已起飞"
  394. # 更新状态表格
  395. self.drone_manager.update_status_table()
  396. QMessageBox.information(self, "无人机控制", "已发送起飞命令至所有无人机")
  397. @pyqtSlot()
  398. def return_all_drones(self):
  399. """返航所有无人机"""
  400. if hasattr(self, 'drone_manager'):
  401. # 切换到无人机管理标签页
  402. for i in range(4): # 假设标签页在索引1-3
  403. if "无人机" in self.findChildren(QTabWidget)[i].tabText(1):
  404. self.findChildren(QTabWidget)[i].setCurrentIndex(1)
  405. break
  406. # 修改所有无人机状态为返航中
  407. for drone_id, drone in self.drone_manager.drones.items():
  408. drone.status = "返航中"
  409. # 更新状态表格
  410. self.drone_manager.update_status_table()
  411. QMessageBox.information(self, "无人机控制", "已发送返航命令至所有无人机")
  412. @pyqtSlot()
  413. def emergency_stop_all_drones(self):
  414. """紧急停止所有无人机"""
  415. if hasattr(self, 'drone_manager'):
  416. reply = QMessageBox.warning(self, "紧急停止确认",
  417. "确定要紧急停止所有无人机吗?这可能导致无人机坠落!",
  418. QMessageBox.Yes | QMessageBox.No,
  419. QMessageBox.No)
  420. if reply == QMessageBox.Yes:
  421. # 切换到无人机管理标签页
  422. for i in range(4): # 假设标签页在索引1-3
  423. if "无人机" in self.findChildren(QTabWidget)[i].tabText(1):
  424. self.findChildren(QTabWidget)[i].setCurrentIndex(1)
  425. break
  426. # 修改所有无人机状态为紧急停止
  427. for drone_id, drone in self.drone_manager.drones.items():
  428. drone.status = "紧急停止"
  429. # 更新状态表格
  430. self.drone_manager.update_status_table()
  431. QMessageBox.critical(self, "无人机控制", "已发送紧急停止命令至所有无人机")
  432. def closeEvent(self, event):
  433. """窗口关闭事件"""
  434. reply = QMessageBox.question(self, '退出确认',
  435. "确定要退出系统吗?",
  436. QMessageBox.Yes | QMessageBox.No,
  437. QMessageBox.No)
  438. if reply == QMessageBox.Yes:
  439. # 停止所有正在运行的线程和定时器
  440. self.update_timer.stop()
  441. self.time_timer.stop()
  442. self.camera_view.stop_monitoring()
  443. # 停止所有无人机
  444. if hasattr(self, 'drone_manager'):
  445. for drone in self.drone_manager.drones.values():
  446. drone.stop()
  447. event.accept()
  448. else:
  449. event.ignore()
  450. def update_overview_stats(self):
  451. """更新右侧统计面板中的数据显示"""
  452. # 更新告警概览组的统计数字
  453. fire_label = self.findChild(QLabel, "fire_overview")
  454. if fire_label:
  455. fire_label.setText(f"火灾告警: {self.statistics_panel.alert_stats['fire']}")
  456. animal_label = self.findChild(QLabel, "animal_overview")
  457. if animal_label:
  458. animal_label.setText(f"动物告警: {self.statistics_panel.alert_stats['animal']}")
  459. landslide_label = self.findChild(QLabel, "landslide_overview")
  460. if landslide_label:
  461. landslide_label.setText(f"滑坡告警: {self.statistics_panel.alert_stats['landslide']}")
  462. pest_label = self.findChild(QLabel, "pest_overview")
  463. if pest_label:
  464. pest_label.setText(f"病虫害: {self.statistics_panel.alert_stats['pest']}")
  465. # 更新各个图表
  466. self.update_overview_chart()
  467. self.update_trend_chart()
  468. self.update_region_chart()
  469. def update_overview_chart(self):
  470. """更新右侧统计面板中的饼图"""
  471. # 获取当前概览组中的饼图
  472. overview_group = self.findChild(QGroupBox, "overview_group")
  473. if overview_group:
  474. # 获取旧的饼图视图
  475. old_chart_view = None
  476. for i in range(overview_group.layout().count()):
  477. item = overview_group.layout().itemAt(i)
  478. if item and item.widget() and isinstance(item.widget(), QChartView):
  479. old_chart_view = item.widget()
  480. break
  481. if old_chart_view:
  482. # 创建新的饼图
  483. new_chart_view = self.statistics_panel.create_pie_chart()
  484. new_chart_view.setObjectName("overview_pie_chart")
  485. new_chart_view.setMinimumHeight(250) # 设置最小高度
  486. new_chart_view.setFixedHeight(250) # 设置固定高度,防止尺寸变化
  487. # 替换旧的饼图
  488. layout = overview_group.layout()
  489. layout.replaceWidget(old_chart_view, new_chart_view)
  490. old_chart_view.deleteLater()
  491. def update_trend_chart(self):
  492. """更新趋势图"""
  493. trend_group = self.findChild(QGroupBox, "trend_group")
  494. if trend_group:
  495. # 获取旧的趋势图
  496. old_chart_view = trend_group.findChild(QChartView, "trend_chart_view")
  497. if old_chart_view:
  498. # 创建新的趋势图
  499. new_chart_view = self.statistics_panel.create_trend_chart_view()
  500. new_chart_view.setObjectName("trend_chart_view")
  501. # 保持尺寸一致
  502. new_chart_view.setMinimumHeight(280)
  503. new_chart_view.setFixedHeight(280)
  504. # 替换旧的趋势图
  505. layout = trend_group.layout()
  506. for i in range(layout.count()):
  507. item = layout.itemAt(i)
  508. if item.widget() == old_chart_view:
  509. layout.replaceWidget(old_chart_view, new_chart_view)
  510. old_chart_view.deleteLater()
  511. break
  512. def update_region_chart(self):
  513. """更新区域统计图"""
  514. region_group = self.findChild(QGroupBox, "region_group")
  515. if region_group:
  516. # 获取旧的区域图
  517. old_chart_view = region_group.findChild(QChartView, "region_chart_view")
  518. if old_chart_view:
  519. # 创建新的区域图
  520. new_chart_view = self.statistics_panel.create_region_chart_view()
  521. new_chart_view.setObjectName("region_chart_view")
  522. # 保持尺寸一致
  523. new_chart_view.setMinimumHeight(280)
  524. new_chart_view.setFixedHeight(280)
  525. # 替换旧的区域图
  526. layout = region_group.layout()
  527. for i in range(layout.count()):
  528. item = layout.itemAt(i)
  529. if item.widget() == old_chart_view:
  530. layout.replaceWidget(old_chart_view, new_chart_view)
  531. old_chart_view.deleteLater()
  532. print("已更新区域统计图")
  533. break
  534. else:
  535. print("警告: 未找到区域图表视图")
  536. else:
  537. print("警告: 未找到区域统计组")
  538. def on_alert_added(self, alert_type, region):
  539. """处理新告警信号,更新统计面板"""
  540. print(f"主窗口收到新告警: 类型={alert_type}, 区域={region}")
  541. # 调用统计面板的方法处理新告警
  542. self.statistics_panel.handle_new_alert(alert_type, region)
  543. # 立即更新右侧统计信息显示
  544. self.update_overview_stats()
  545. # 强制更新区域图表
  546. self.update_region_chart()
  547. # 重置随机更新定时器,避免冲突
  548. self.statistics_panel.update_timer.stop()
  549. self.statistics_panel.update_timer.start(10000) # 增加到10秒,减少干扰
  550. # 强制立即刷新所有图表
  551. QApplication.processEvents() # 确保UI更新立即可见
  552. def on_alert_processed(self, alert_type, region):
  553. """处理告警处理信号,更新统计面板"""
  554. print(f"主窗口收到告警处理: 类型={alert_type}, 区域={region}")
  555. # 调用统计面板的方法处理告警处理
  556. self.statistics_panel.handle_alert_processed(alert_type, region)
  557. # 立即更新右侧统计信息显示
  558. self.update_overview_stats()
  559. # 强制更新区域图表
  560. self.update_region_chart()
  561. # 重置随机更新定时器,避免冲突
  562. self.statistics_panel.update_timer.stop()
  563. self.statistics_panel.update_timer.start(10000) # 增加到10秒,减少干扰
  564. # 强制立即刷新所有图表
  565. QApplication.processEvents() # 确保UI更新立即可见
  566. def on_fire_detected(self, region):
  567. """处理火灾检测信号"""
  568. current_time = datetime.now().timestamp()
  569. # 检查是否达到最小更新间隔
  570. if current_time - self.last_fire_update_time < self.fire_update_interval:
  571. return # 如果间隔太短,直接返回不处理
  572. print(f"收到火灾检测信号,区域:{region}")
  573. # 更新上次处理时间
  574. self.last_fire_update_time = current_time
  575. # 创建并添加告警
  576. alert = {
  577. 'type': 'fire',
  578. 'location': region,
  579. 'time': datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
  580. 'detail': '检测到火灾隐患',
  581. 'level': 'high',
  582. 'status': '未处理'
  583. }
  584. self.alert_panel.add_alert(alert) # 这会触发alert_added信号,统计更新将在on_alert_added中处理
  585. def on_animal_detected(self, image, species, confidence):
  586. """处理动物检测信号"""
  587. current_time = time.time()
  588. if current_time - self.last_animal_update < self.min_update_interval:
  589. return
  590. self.last_animal_update = current_time
  591. # 创建动物检测告警
  592. alert = {
  593. 'type': 'animal',
  594. 'location': '未知位置', # 可以根据摄像头位置更新
  595. 'time': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
  596. 'status': 'unprocessed',
  597. 'details': f'检测到{species},置信度: {confidence:.1f}%',
  598. 'image': image # 保存检测到动物的图像
  599. }
  600. # 添加到告警面板
  601. self.alert_panel.add_alert(alert)
  602. # 调用统计面板的方法处理新告警
  603. self.statistics_panel.handle_new_alert('animal', '未知位置')
  604. # 立即更新右侧统计信息显示
  605. self.update_overview_stats()
  606. # 强制更新区域图表
  607. self.update_region_chart()
  608. # 重置随机更新定时器,避免冲突
  609. self.statistics_panel.update_timer.stop()
  610. self.statistics_panel.update_timer.start(10000) # 增加到10秒,减少干扰
  611. # 强制立即刷新所有图表
  612. QApplication.processEvents() # 确保UI更新立即可见