diff --git a/3rd/about/__init__.py b/3rd/about/__init__.py
new file mode 100644
index 0000000..77caec2
--- /dev/null
+++ b/3rd/about/__init__.py
@@ -0,0 +1 @@
+from about.main import *
\ No newline at end of file
diff --git a/3rd/about/main.py b/3rd/about/main.py
new file mode 100644
index 0000000..a6e0e4e
--- /dev/null
+++ b/3rd/about/main.py
@@ -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("
"+ QApplication.applicationName() + "
")
+ self.label.setAlignment(Qt.AlignCenter)
+ self.label.setStyleSheet("font-size: 20px;")
+
+ self.label2 = QLabel("Version: " + QApplication.applicationVersion() + "
")
+ self.label2.setAlignment(Qt.AlignCenter)
+ self.label2.setStyleSheet("font-size: 15px;")
+
+ self.label3 = QLabel("" + QApplication.organizationName() + "
")
+ self.label3.setAlignment(Qt.AlignCenter)
+ self.label3.setStyleSheet("font-size: 15px;")
+
+ self.label4 = QLabel("Copyright (c) 2020
")
+ 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()
\ No newline at end of file
diff --git a/plugins/about/__init__.py b/plugins/about/__init__.py
new file mode 100644
index 0000000..77caec2
--- /dev/null
+++ b/plugins/about/__init__.py
@@ -0,0 +1 @@
+from about.main import *
\ No newline at end of file
diff --git a/plugins/about/main.py b/plugins/about/main.py
index c84eb68..a6e0e4e 100644
--- a/plugins/about/main.py
+++ b/plugins/about/main.py
@@ -46,13 +46,23 @@ class AboutDialog(QDialog):
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['menubar'])
+ action = QAction('&关于', self.ctx['menu_bar'])
action.triggered.connect(self.on_about)
menu.addAction(action)
def on_about(self):
- dialog = AboutDialog(self.ctx['main_window'])
+ print('on_about')
+ dialog = AboutDialog(self.ctx['mainwindow'])
dialog.show()
\ No newline at end of file
diff --git a/rscder/gui/actions.py b/rscder/gui/actions.py
index c698d0e..485e102 100644
--- a/rscder/gui/actions.py
+++ b/rscder/gui/actions.py
@@ -47,6 +47,21 @@ class ActionManager(QtCore.QObject):
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):
self.toolbar = toolbar
self.toolbar.setIconSize(QtCore.QSize(24, 24))
diff --git a/rscder/gui/license.py b/rscder/gui/license.py
index 058fc73..0d2033a 100644
--- a/rscder/gui/license.py
+++ b/rscder/gui/license.py
@@ -11,7 +11,7 @@ class License(QtWidgets.QDialog):
def __init__(self, parent = None, flags = QtCore.Qt.WindowFlags() ) -> None:
super().__init__(parent, flags)
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.setFixedSize(600, 400)
diff --git a/rscder/gui/mainwindow.py b/rscder/gui/mainwindow.py
index 358e33b..9c37776 100644
--- a/rscder/gui/mainwindow.py
+++ b/rscder/gui/mainwindow.py
@@ -9,6 +9,7 @@ from rscder.gui.layertree import LayerTree
from rscder.gui.mapcanvas import DoubleCanvas
from rscder.gui.messagebox import MessageBox
from rscder.gui.result import ResultTable
+from rscder.plugins.loader import PluginLoader
from rscder.utils import Settings
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_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)
diff --git a/rscder/gui/plugins.py b/rscder/gui/plugins.py
index 098c2d9..7a80afd 100644
--- a/rscder/gui/plugins.py
+++ b/rscder/gui/plugins.py
@@ -1,5 +1,9 @@
+import os
+import shutil
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
class PluginDialog(QDialog):
@@ -8,25 +12,25 @@ class PluginDialog(QDialog):
super().__init__(parent)
self.setWindowTitle('Plugins')
self.setWindowIcon(QIcon(":/icons/logo.svg"))
- self.setMinimumWidth(800)
+ self.setMinimumWidth(900)
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.setColumnWidth(0, 200)
self.plugin_table.setColumnWidth(1, 500)
- self.plugin_table.setHorizontalHeaderLabels(['Name', 'Path', 'Enabled'])
- self.plugin_table.setEditTriggers(QAbstractItemView.DoubleClicked)
+ self.plugin_table.setHorizontalHeaderLabels(['Name', 'Module', 'Enabled'])
+ self.plugin_table.setEditTriggers(QAbstractItemView.NoEditTriggers)
self.plugin_table.cellDoubleClicked.connect(self.edit_plugin)
for idx, plugin in enumerate(self.plugins):
name_item = QTableWidgetItem(plugin['name'])
- path_item = QTableWidgetItem(plugin['path'])
+ module_item = QTableWidgetItem(plugin['module'])
enabled_item = QTableWidgetItem()
enabled_item.setCheckState(Qt.Checked if plugin['enabled'] else Qt.Unchecked)
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.add_button = QPushButton('Add', self)
@@ -50,39 +54,58 @@ class PluginDialog(QDialog):
self.has_change = False
def add_plugin(self):
- self.has_change = True
- self.plugin_table.insertRow(self.plugin_table.rowCount())
+ plugin_directory = QFileDialog.getExistingDirectory(self, 'Select Plugin Directory', '.')
+ 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):
self.has_change = True
- for row in self.plugin_table.selectedItems():
- self.plugin_table.removeRow(row.row())
-
+ row_ids = list( row.row() for row in self.plugin_table.selectionModel().selectedRows())
+ 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
def edit_plugin(self, row, column):
self.has_change = True
- if column == 0:
- 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:
+ if column == 2:
self.plugin_table.item(row, column).setFlags(Qt.ItemIsEnabled | Qt.ItemIsSelectable)
- # self.plugin_list.setFixedWidth(200)
def save_plugin(self):
- plugins = []
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
- plugins.append({'name': name, 'path': path, 'enabled': enabled})
- Settings.Plugin().plugins = plugins
+ self.plugins[idx]['enabled'] = enabled
+ Settings.Plugin().plugins = self.plugins
+ self.has_change = False
self.close()
def closeEvent(self, event):
diff --git a/rscder/mul/mulstart.py b/rscder/mul/mulstart.py
index a132671..e0a4717 100644
--- a/rscder/mul/mulstart.py
+++ b/rscder/mul/mulstart.py
@@ -5,10 +5,10 @@ from PyQt5.QtGui import QFont, QPixmap
from PyQt5.QtWidgets import QSplashScreen, QProgressBar, QStyleFactory, QMessageBox
from qgis.core import QgsApplication
# from qgis.core import
-from gui.mainwindow import MainWindow
+from rscder.gui.mainwindow import MainWindow
import multiprocessing
-from gui import license
-from utils.setting import Settings
+from rscder.gui import license
+from rscder.utils.setting import Settings
class MulStart:
@@ -27,7 +27,7 @@ class MulStart:
# pyrcc5 res.qrc -o rc.py
- import rc
+ import rscder.rc
app = QgsApplication([], True)
QgsApplication.initQgis()
diff --git a/rscder/plugins/basic.py b/rscder/plugins/basic.py
index b57bd36..905cc32 100644
--- a/rscder/plugins/basic.py
+++ b/rscder/plugins/basic.py
@@ -1,6 +1,7 @@
from PyQt5.QtCore import QObject, pyqtSignal
from rscder.utils.project import PairLayer
+
class BasicPlugin(QObject):
'''
插件基类
@@ -15,11 +16,16 @@ class BasicPlugin(QObject):
statusbar: statusbar
menu: menu
file_menu: file menu
-
-
'''
+ @staticmethod
+ def info():
+ '''
+ Plugin info
+ '''
+ raise NotImplementedError
+
def __init__(self, ctx:dict) -> None:
- super().__init__()
+ super().__init__(ctx['mainwindow'])
self.ctx = ctx
self.layer_tree = ctx['layer_tree']
self.pair_canvas = ctx['pair_canvas']
@@ -29,7 +35,7 @@ class BasicPlugin(QObject):
self.mainwindow = ctx['mainwindow']
self.set_action()
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):
diff --git a/rscder/plugins/loader.py b/rscder/plugins/loader.py
index 18543fc..635e133 100644
--- a/rscder/plugins/loader.py
+++ b/rscder/plugins/loader.py
@@ -1,4 +1,6 @@
+import shutil
from rscder.utils.setting import Settings
+from PyQt5.QtWidgets import QMessageBox
from PyQt5.QtCore import QObject, pyqtSignal
from rscder.plugins.basic import BasicPlugin
import importlib
@@ -11,20 +13,54 @@ class PluginLoader(QObject):
plugin_loaded = pyqtSignal()
def __init__(self, ctx):
+ super().__init__()
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):
plugins = Settings.Plugin().plugins
+ if Settings.Plugin().root not in sys.path:
+ sys.path.insert(0, Settings.Plugin().root)
for plugin in plugins:
- name = plugin['name']
- path = plugin['path']
+ # path = plugin['path']
+ if not plugin['enabled']:
+ continue
try:
- module = importlib.import_module(path)
+ module = importlib.import_module(plugin['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:
- obj(self.ctx)
+ if inspect.isclass(obj) and issubclass(obj, BasicPlugin) and obj != BasicPlugin and obj != PluginLoader:
+ self.plugins.append(obj(self.ctx))
+ break
except Exception as e:
- self.ctx['message_box'].error(f'{name} load error: {e}')
- # print(e)
+ self.ctx['message_box'].error(f'{plugin["name"]} load error: {e}')
+
self.plugin_loaded.emit()
\ No newline at end of file
diff --git a/rscder/res.qrc b/rscder/res.qrc
index 07a812c..76b52ba 100644
--- a/rscder/res.qrc
+++ b/rscder/res.qrc
@@ -1,26 +1,26 @@
- icons/splash.png
- icons/logo.svg
- icons/load.svg
- icons/settings.svg
- icons/exit.svg
- icons/vector.svg
- icons/zoomin.svg
- icons/zoomout.svg
- icons/pan.svg
- icons/full.svg
- icons/start.svg
- icons/clear.svg
- icons/edit.svg
- icons/export.svg
- icons/model.svg
- icons/assessment.svg
- icons/paint.svg
- icons/font.svg
- icons/qt.svg
- icons/ok.svg
- icons/cancel.svg
- icons/outline.svg
+ icons\assessment.svg
+ icons\cancel.svg
+ icons\clear.svg
+ icons\edit.svg
+ icons\exit.svg
+ icons\export.svg
+ icons\font.svg
+ icons\full.svg
+ icons\load.svg
+ icons\logo.svg
+ icons\model.svg
+ icons\ok.svg
+ icons\outline.svg
+ icons\paint.svg
+ icons\pan.svg
+ icons\qt.svg
+ icons\settings.svg
+ icons\splash.png
+ icons\start.svg
+ icons\vector.svg
+ icons\zoomin.svg
+ icons\zoomout.svg
diff --git a/rscder/utils/generate_rc.py b/rscder/utils/generate_rc.py
new file mode 100644
index 0000000..8cd4cbb
--- /dev/null
+++ b/rscder/utils/generate_rc.py
@@ -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'\n')
+ f.write(f' \n')
+ for icon in os.listdir(icon_path):
+ f.write(f' {os.path.join("icons", icon)}\n')
+ f.write(f' \n')
+ f.write(f'\n')
+
+subprocess.run(['pyrcc5', 'res.qrc', '-o', 'rc.py'], cwd=os.path.join(path, '..'))
+
+shutil.rmtree(icon_path)
\ No newline at end of file
diff --git a/rscder/utils/project.py b/rscder/utils/project.py
index 26853a8..b0b5c9e 100644
--- a/rscder/utils/project.py
+++ b/rscder/utils/project.py
@@ -3,7 +3,7 @@ from pathlib import Path
from typing import Dict, List
import uuid
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 PyQt5.QtCore import QObject, pyqtSignal
from PyQt5.QtGui import QColor
diff --git a/rscder/utils/setting.py b/rscder/utils/setting.py
index 0ea5673..c32103c 100644
--- a/rscder/utils/setting.py
+++ b/rscder/utils/setting.py
@@ -27,8 +27,10 @@ class Settings(QSettings):
@property
def plugins(self):
with Settings(Settings.Plugin.PRE) as s:
- return s.value('plugins', [])
-
+ pl = s.value('plugins', [])
+ if pl is None:
+ return []
+ return pl
@plugins.setter
def plugins(self, value):
with Settings(Settings.Plugin.PRE) as s:
diff --git a/main.py b/run.py
similarity index 100%
rename from main.py
rename to run.py