fix plugin bugs

This commit is contained in:
copper 2022-05-05 20:08:52 +08:00
parent 00ffe0c03f
commit df27d40587
16 changed files with 262 additions and 71 deletions

1
3rd/about/__init__.py Normal file
View File

@ -0,0 +1 @@
from about.main import *

68
3rd/about/main.py Normal file
View File

@ -0,0 +1,68 @@
from rscder.plugins.basic import BasicPlugin
from PyQt5.QtWidgets import QDialog, QAction, QApplication, QLabel, QTextEdit, QVBoxLayout
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QIcon
class AboutDialog(QDialog):
def __init__(self, parent=None):
super(AboutDialog, self).__init__(parent)
self.setWindowTitle("About")
self.setFixedSize(800, 400)
self.label = QLabel("<h1>"+ QApplication.applicationName() + "</h1>")
self.label.setAlignment(Qt.AlignCenter)
self.label.setStyleSheet("font-size: 20px;")
self.label2 = QLabel("<h2>Version: " + QApplication.applicationVersion() + "</h2>")
self.label2.setAlignment(Qt.AlignCenter)
self.label2.setStyleSheet("font-size: 15px;")
self.label3 = QLabel("<h2>" + QApplication.organizationName() + "</h2>")
self.label3.setAlignment(Qt.AlignCenter)
self.label3.setStyleSheet("font-size: 15px;")
self.label4 = QLabel("<h3>Copyright (c) 2020</h3>")
self.label4.setAlignment(Qt.AlignCenter)
self.label4.setStyleSheet("font-size: 10px;")
self.text = QTextEdit()
self.text.setReadOnly(True)
self.text.setText('''
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
''')
self.layout = QVBoxLayout()
self.layout.addWidget(self.label)
self.layout.addWidget(self.label2)
self.layout.addWidget(self.label3)
self.layout.addWidget(self.label4)
self.layout.addWidget(self.text)
self.setLayout(self.layout)
class AboutPlugin(BasicPlugin):
@staticmethod
def info():
return {
'name': '关于',
'author': 'RSCDER',
'version': '1.0.0',
'description': '关于'
}
def set_action(self):
menu = self.ctx['help_menu']
action = QAction('&关于', self.ctx['menu_bar'])
action.triggered.connect(self.on_about)
menu.addAction(action)
def on_about(self):
print('on_about')
dialog = AboutDialog(self.ctx['mainwindow'])
dialog.show()

View File

@ -0,0 +1 @@
from about.main import *

View File

@ -46,13 +46,23 @@ class AboutDialog(QDialog):
class AboutPlugin(BasicPlugin): class AboutPlugin(BasicPlugin):
@staticmethod
def info():
return {
'name': '关于',
'author': 'RSCDER',
'version': '1.0.0',
'description': '关于'
}
def set_action(self): def set_action(self):
menu = self.ctx['help_menu'] menu = self.ctx['help_menu']
action = QAction('&关于', self.ctx['menubar']) action = QAction('&关于', self.ctx['menu_bar'])
action.triggered.connect(self.on_about) action.triggered.connect(self.on_about)
menu.addAction(action) menu.addAction(action)
def on_about(self): def on_about(self):
dialog = AboutDialog(self.ctx['main_window']) print('on_about')
dialog = AboutDialog(self.ctx['mainwindow'])
dialog.show() dialog.show()

View File

@ -47,6 +47,21 @@ class ActionManager(QtCore.QObject):
self.help_menu = menubar.addMenu('&帮助') self.help_menu = menubar.addMenu('&帮助')
@property
def menus(self):
return {
'file_menu': self.file_menu,
'basic_menu': self.basic_menu,
'change_detection_menu': self.change_detection_menu,
'special_chagne_detec_menu': self.special_chagne_detec_menu,
'seg_chagne_detec_menu': self.seg_chagne_detec_menu,
'postop_menu': self.postop_menu,
'view_menu': self.view_menu,
'plugin_menu': self.plugin_menu,
'help_menu': self.help_menu,
'menu_bar': self.menubar
}
def set_toolbar(self, toolbar): def set_toolbar(self, toolbar):
self.toolbar = toolbar self.toolbar = toolbar
self.toolbar.setIconSize(QtCore.QSize(24, 24)) self.toolbar.setIconSize(QtCore.QSize(24, 24))

View File

@ -11,7 +11,7 @@ class License(QtWidgets.QDialog):
def __init__(self, parent = None, flags = QtCore.Qt.WindowFlags() ) -> None: def __init__(self, parent = None, flags = QtCore.Qt.WindowFlags() ) -> None:
super().__init__(parent, flags) super().__init__(parent, flags)
self.setWindowTitle("License") self.setWindowTitle("License")
self.setWindowIcon(QIcon(os.path.join("gui", "icons", "license.png"))) self.setWindowIcon(QIcon(':/icons/license.png'))
self.setWindowFlags(QtCore.Qt.WindowCloseButtonHint) self.setWindowFlags(QtCore.Qt.WindowCloseButtonHint)
self.setFixedSize(600, 400) self.setFixedSize(600, 400)

View File

@ -9,6 +9,7 @@ from rscder.gui.layertree import LayerTree
from rscder.gui.mapcanvas import DoubleCanvas from rscder.gui.mapcanvas import DoubleCanvas
from rscder.gui.messagebox import MessageBox from rscder.gui.messagebox import MessageBox
from rscder.gui.result import ResultTable from rscder.gui.result import ResultTable
from rscder.plugins.loader import PluginLoader
from rscder.utils import Settings from rscder.utils import Settings
from rscder.utils.project import Project from rscder.utils.project import Project
@ -43,6 +44,18 @@ class MainWindow(QMainWindow):
self.action_manager.set_status_bar(self.statusBar()) self.action_manager.set_status_bar(self.statusBar())
self.action_manager.set_actions() self.action_manager.set_actions()
PluginLoader(dict(
layer_tree=self.layer_tree,
pair_canvas=self.double_map,
message_box=self.message_box,
result_table=self.result_box,
project=Project(self),
mainwindow=self,
toolbar=self.toolbar,
statusbar=self.statusBar(),
**self.action_manager.menus
)).load_plugin()
self.resize(*Settings.General().size) self.resize(*Settings.General().size)

View File

@ -1,5 +1,9 @@
import os
import shutil
from PyQt5.QtWidgets import * from PyQt5.QtWidgets import *
from PyQt5.QtGui import QIcon, Qt from PyQt5.QtGui import QIcon
from PyQt5.QtCore import Qt
from rscder.plugins.loader import PluginLoader
from rscder.utils.setting import Settings from rscder.utils.setting import Settings
class PluginDialog(QDialog): class PluginDialog(QDialog):
@ -8,25 +12,25 @@ class PluginDialog(QDialog):
super().__init__(parent) super().__init__(parent)
self.setWindowTitle('Plugins') self.setWindowTitle('Plugins')
self.setWindowIcon(QIcon(":/icons/logo.svg")) self.setWindowIcon(QIcon(":/icons/logo.svg"))
self.setMinimumWidth(800) self.setMinimumWidth(900)
self.setMinimumHeight(600) self.setMinimumHeight(600)
self.plugins = Settings.Plugin().plugins self.plugins = list(Settings.Plugin().plugins)
self.plugin_table = QTableWidget(len(self.plugins), 2, self) self.plugin_table = QTableWidget(len(self.plugins), 3, self)
self.plugin_table.setSelectionMode(QAbstractItemView.ExtendedSelection) self.plugin_table.setSelectionMode(QAbstractItemView.ExtendedSelection)
self.plugin_table.setColumnWidth(0, 200) self.plugin_table.setColumnWidth(0, 200)
self.plugin_table.setColumnWidth(1, 500) self.plugin_table.setColumnWidth(1, 500)
self.plugin_table.setHorizontalHeaderLabels(['Name', 'Path', 'Enabled']) self.plugin_table.setHorizontalHeaderLabels(['Name', 'Module', 'Enabled'])
self.plugin_table.setEditTriggers(QAbstractItemView.DoubleClicked) self.plugin_table.setEditTriggers(QAbstractItemView.NoEditTriggers)
self.plugin_table.cellDoubleClicked.connect(self.edit_plugin) self.plugin_table.cellDoubleClicked.connect(self.edit_plugin)
for idx, plugin in enumerate(self.plugins): for idx, plugin in enumerate(self.plugins):
name_item = QTableWidgetItem(plugin['name']) name_item = QTableWidgetItem(plugin['name'])
path_item = QTableWidgetItem(plugin['path']) module_item = QTableWidgetItem(plugin['module'])
enabled_item = QTableWidgetItem() enabled_item = QTableWidgetItem()
enabled_item.setCheckState(Qt.Checked if plugin['enabled'] else Qt.Unchecked) enabled_item.setCheckState(Qt.Checked if plugin['enabled'] else Qt.Unchecked)
self.plugin_table.setItem(idx, 0, name_item) self.plugin_table.setItem(idx, 0, name_item)
self.plugin_table.setItem(idx, 1, path_item) self.plugin_table.setItem(idx, 1, module_item)
self.plugin_table.setItem(idx, 2, enabled_item) self.plugin_table.setItem(idx, 2, enabled_item)
self.add_button = QPushButton('Add', self) self.add_button = QPushButton('Add', self)
@ -50,39 +54,58 @@ class PluginDialog(QDialog):
self.has_change = False self.has_change = False
def add_plugin(self): def add_plugin(self):
self.has_change = True plugin_directory = QFileDialog.getExistingDirectory(self, 'Select Plugin Directory', '.')
self.plugin_table.insertRow(self.plugin_table.rowCount()) if plugin_directory is not None:
info = PluginLoader.load_plugin_info(plugin_directory)
print(info)
if info is not None:
info['module'] = os.path.basename(plugin_directory)
info['enabled'] = True
self.has_change = True
self.plugins.append(info)
self.plugin_table.insertRow(self.plugin_table.rowCount())
name_item = QTableWidgetItem(info['name'])
module_item = QTableWidgetItem(info['module'])
enabled_item = QTableWidgetItem('启用')
enabled_item.setCheckState(Qt.Checked)
self.plugin_table.setItem(self.plugin_table.rowCount() - 1, 0, name_item)
self.plugin_table.setItem(self.plugin_table.rowCount() - 1, 1, module_item)
self.plugin_table.setItem(self.plugin_table.rowCount() - 1, 2, enabled_item)
dst = PluginLoader.copy_plugin_to_3rd(plugin_directory)
if dst is not None:
self.plugins[-1]['path'] = dst
else:
pass
def remove_plugin(self): def remove_plugin(self):
self.has_change = True self.has_change = True
for row in self.plugin_table.selectedItems(): row_ids = list( row.row() for row in self.plugin_table.selectionModel().selectedRows())
self.plugin_table.removeRow(row.row()) row_ids.sort(reverse=True)
for row in row_ids:
self.plugin_table.removeRow(row)
info = self.plugins.pop(row)
try:
shutil.rmtree(info['path'])
except:
pass
# for idx in self.plugins # for idx in self.plugins
def edit_plugin(self, row, column): def edit_plugin(self, row, column):
self.has_change = True self.has_change = True
if column == 0: if column == 2:
self.plugin_table.item(row, column).setFlags(Qt.ItemIsEnabled | Qt.ItemIsSelectable)
elif column == 1:
open_file = QFileDialog.getOpenFileName(self, 'Open File', '', 'Python Files (*.py)')
if open_file[0]:
self.plugin_table.item(row, column).setText(open_file[0])
else:
pass
elif column == 2:
self.plugin_table.item(row, column).setFlags(Qt.ItemIsEnabled | Qt.ItemIsSelectable) self.plugin_table.item(row, column).setFlags(Qt.ItemIsEnabled | Qt.ItemIsSelectable)
# self.plugin_list.setFixedWidth(200)
def save_plugin(self): def save_plugin(self):
plugins = []
for idx in range(self.plugin_table.rowCount()): for idx in range(self.plugin_table.rowCount()):
name = self.plugin_table.item(idx, 0).text()
path = self.plugin_table.item(idx, 1).text()
enabled = self.plugin_table.item(idx, 2).checkState() == Qt.Checked enabled = self.plugin_table.item(idx, 2).checkState() == Qt.Checked
plugins.append({'name': name, 'path': path, 'enabled': enabled}) self.plugins[idx]['enabled'] = enabled
Settings.Plugin().plugins = plugins Settings.Plugin().plugins = self.plugins
self.has_change = False
self.close() self.close()
def closeEvent(self, event): def closeEvent(self, event):

View File

@ -5,10 +5,10 @@ from PyQt5.QtGui import QFont, QPixmap
from PyQt5.QtWidgets import QSplashScreen, QProgressBar, QStyleFactory, QMessageBox from PyQt5.QtWidgets import QSplashScreen, QProgressBar, QStyleFactory, QMessageBox
from qgis.core import QgsApplication from qgis.core import QgsApplication
# from qgis.core import # from qgis.core import
from gui.mainwindow import MainWindow from rscder.gui.mainwindow import MainWindow
import multiprocessing import multiprocessing
from gui import license from rscder.gui import license
from utils.setting import Settings from rscder.utils.setting import Settings
class MulStart: class MulStart:
@ -27,7 +27,7 @@ class MulStart:
# pyrcc5 res.qrc -o rc.py # pyrcc5 res.qrc -o rc.py
import rc import rscder.rc
app = QgsApplication([], True) app = QgsApplication([], True)
QgsApplication.initQgis() QgsApplication.initQgis()

View File

@ -1,6 +1,7 @@
from PyQt5.QtCore import QObject, pyqtSignal from PyQt5.QtCore import QObject, pyqtSignal
from rscder.utils.project import PairLayer from rscder.utils.project import PairLayer
class BasicPlugin(QObject): class BasicPlugin(QObject):
''' '''
插件基类 插件基类
@ -15,11 +16,16 @@ class BasicPlugin(QObject):
statusbar: statusbar statusbar: statusbar
menu: menu menu: menu
file_menu: file menu file_menu: file menu
''' '''
@staticmethod
def info():
'''
Plugin info
'''
raise NotImplementedError
def __init__(self, ctx:dict) -> None: def __init__(self, ctx:dict) -> None:
super().__init__() super().__init__(ctx['mainwindow'])
self.ctx = ctx self.ctx = ctx
self.layer_tree = ctx['layer_tree'] self.layer_tree = ctx['layer_tree']
self.pair_canvas = ctx['pair_canvas'] self.pair_canvas = ctx['pair_canvas']
@ -29,7 +35,7 @@ class BasicPlugin(QObject):
self.mainwindow = ctx['mainwindow'] self.mainwindow = ctx['mainwindow']
self.set_action() self.set_action()
self.project.layer_load.connect(self.on_data_load) self.project.layer_load.connect(self.on_data_load)
self.project.project_created.connect(self.setup) self.project.project_init.connect(self.setup)
def set_action(self): def set_action(self):

View File

@ -1,4 +1,6 @@
import shutil
from rscder.utils.setting import Settings from rscder.utils.setting import Settings
from PyQt5.QtWidgets import QMessageBox
from PyQt5.QtCore import QObject, pyqtSignal from PyQt5.QtCore import QObject, pyqtSignal
from rscder.plugins.basic import BasicPlugin from rscder.plugins.basic import BasicPlugin
import importlib import importlib
@ -11,20 +13,54 @@ class PluginLoader(QObject):
plugin_loaded = pyqtSignal() plugin_loaded = pyqtSignal()
def __init__(self, ctx): def __init__(self, ctx):
super().__init__()
self.ctx = ctx self.ctx = ctx
self.plugins = []
@staticmethod
def copy_plugin_to_3rd(dir, random_suffix=True):
if not os.path.exists(Settings.Plugin().root):
os.makedirs(Settings.Plugin().root)
return shutil.copytree(dir,
os.path.join(Settings.Plugin().root,
os.path.basename(dir)))
@staticmethod
def load_plugin_info(path):
sys.path.insert(0, os.path.join(path, '..'))
info = None
try:
module = importlib.import_module(os.path.basename(path))
mes = inspect.getmembers(module)
for name, obj in mes:
print(name, obj)
if inspect.isclass(obj) and issubclass(obj, BasicPlugin):
info = obj.info()
break
except Exception as e:
print(e)
QMessageBox.critical(None, 'Error', f'{path} load error: {e}')
finally:
sys.path.pop(0)
return info
def load_plugin(self): def load_plugin(self):
plugins = Settings.Plugin().plugins plugins = Settings.Plugin().plugins
if Settings.Plugin().root not in sys.path:
sys.path.insert(0, Settings.Plugin().root)
for plugin in plugins: for plugin in plugins:
name = plugin['name'] # path = plugin['path']
path = plugin['path'] if not plugin['enabled']:
continue
try: try:
module = importlib.import_module(path) module = importlib.import_module(plugin['module'])
for oname, obj in inspect.getmembers(module): for oname, obj in inspect.getmembers(module):
if inspect.isclass(obj) and issubclass(obj, BasicPlugin) and obj != BasicPlugin and obj != PluginLoader and oname == name: if inspect.isclass(obj) and issubclass(obj, BasicPlugin) and obj != BasicPlugin and obj != PluginLoader:
obj(self.ctx) self.plugins.append(obj(self.ctx))
break
except Exception as e: except Exception as e:
self.ctx['message_box'].error(f'{name} load error: {e}') self.ctx['message_box'].error(f'{plugin["name"]} load error: {e}')
# print(e)
self.plugin_loaded.emit() self.plugin_loaded.emit()

View File

@ -1,26 +1,26 @@
<RCC> <RCC>
<qresource prefix="/"> <qresource prefix="/">
<file>icons/splash.png</file> <file>icons\assessment.svg</file>
<file>icons/logo.svg</file> <file>icons\cancel.svg</file>
<file>icons/load.svg</file> <file>icons\clear.svg</file>
<file>icons/settings.svg</file> <file>icons\edit.svg</file>
<file>icons/exit.svg</file> <file>icons\exit.svg</file>
<file>icons/vector.svg</file> <file>icons\export.svg</file>
<file>icons/zoomin.svg</file> <file>icons\font.svg</file>
<file>icons/zoomout.svg</file> <file>icons\full.svg</file>
<file>icons/pan.svg</file> <file>icons\load.svg</file>
<file>icons/full.svg</file> <file>icons\logo.svg</file>
<file>icons/start.svg</file> <file>icons\model.svg</file>
<file>icons/clear.svg</file> <file>icons\ok.svg</file>
<file>icons/edit.svg</file> <file>icons\outline.svg</file>
<file>icons/export.svg</file> <file>icons\paint.svg</file>
<file>icons/model.svg</file> <file>icons\pan.svg</file>
<file>icons/assessment.svg</file> <file>icons\qt.svg</file>
<file>icons/paint.svg</file> <file>icons\settings.svg</file>
<file>icons/font.svg</file> <file>icons\splash.png</file>
<file>icons/qt.svg</file> <file>icons\start.svg</file>
<file>icons/ok.svg</file> <file>icons\vector.svg</file>
<file>icons/cancel.svg</file> <file>icons\zoomin.svg</file>
<file>icons/outline.svg</file> <file>icons\zoomout.svg</file>
</qresource> </qresource>
</RCC> </RCC>

View File

@ -0,0 +1,16 @@
import shutil
import subprocess
import os
path = os.path.dirname(os.path.realpath(__file__))
icon_path = os.path.join(path, '..', 'icons')
with open(os.path.join(path, '..', 'res.qrc'), 'w') as f:
f.write(f'<RCC>\n')
f.write(f' <qresource prefix="/">\n')
for icon in os.listdir(icon_path):
f.write(f' <file>{os.path.join("icons", icon)}</file>\n')
f.write(f' </qresource>\n')
f.write(f'</RCC>\n')
subprocess.run(['pyrcc5', 'res.qrc', '-o', 'rc.py'], cwd=os.path.join(path, '..'))
shutil.rmtree(icon_path)

View File

@ -3,7 +3,7 @@ from pathlib import Path
from typing import Dict, List from typing import Dict, List
import uuid import uuid
from osgeo import gdal, gdal_array from osgeo import gdal, gdal_array
from utils.setting import Settings from rscder.utils.setting import Settings
from qgis.core import QgsRasterLayer, QgsLineSymbol, QgsSingleSymbolRenderer, QgsSimpleLineSymbolLayer, QgsVectorLayer, QgsCoordinateReferenceSystem, QgsFeature, QgsGeometry, QgsPointXY from qgis.core import QgsRasterLayer, QgsLineSymbol, QgsSingleSymbolRenderer, QgsSimpleLineSymbolLayer, QgsVectorLayer, QgsCoordinateReferenceSystem, QgsFeature, QgsGeometry, QgsPointXY
from PyQt5.QtCore import QObject, pyqtSignal from PyQt5.QtCore import QObject, pyqtSignal
from PyQt5.QtGui import QColor from PyQt5.QtGui import QColor

View File

@ -27,8 +27,10 @@ class Settings(QSettings):
@property @property
def plugins(self): def plugins(self):
with Settings(Settings.Plugin.PRE) as s: with Settings(Settings.Plugin.PRE) as s:
return s.value('plugins', []) pl = s.value('plugins', [])
if pl is None:
return []
return pl
@plugins.setter @plugins.setter
def plugins(self, value): def plugins(self, value):
with Settings(Settings.Plugin.PRE) as s: with Settings(Settings.Plugin.PRE) as s:

View File