add the first plugins

This commit is contained in:
2021-11-26 18:19:57 +01:00
parent 1ea10e2933
commit 0e27cb2b26
13 changed files with 543 additions and 197 deletions

268
raven/mainwindow.py Normal file
View File

@@ -0,0 +1,268 @@
import logging
import os
import sys
from typing import List, Optional
from PyQt6.QtWidgets import *
from PyQt6.QtGui import *
import constants
import urlutils
from aboutdialog import AboutDialog
from cutesettings import CuteSettings
from raven.pluginregistry import PluginRegistry
from raven.plugins.domain.menucontribution import MenuContribution
from raven.plugins.domain.raction import RAction
from raven.plugins.domain.rmenu import RMenu
from settingsstore import SettingsStore
from highlightingdialog import HighlightingDialog
from tabs import Tabs
from urlutils import url_is_file
from functools import reduce
MAX_LINE_LENGTH = 4096
logging.basicConfig(level=logging.INFO)
log = logging.getLogger("main")
def flat_map(array: List[List]) -> List:
return reduce(list.__add__, array)
def _action_about():
about_action = RAction(
"&About",
action=lambda: AboutDialog().exec(),
icon_file=constants.raven_icon
)
return about_action
class MainWindow(QMainWindow):
def __init__(self, *args, **kwargs):
super(MainWindow, self).__init__(*args, **kwargs)
self.settings = SettingsStore.load()
PluginRegistry.execute("set_settings", self.settings)
PluginRegistry.execute("set_translator", lambda string: self.tr(string))
self.setWindowTitle(self.tr("RavenLog"))
self._restore_window()
self.setDockNestingEnabled(True)
self.setAcceptDrops(True)
self.tabs = Tabs(self.settings)
self._menu_recent_files = QMenu(self.tr("Open &Recent"), self)
self.setCentralWidget(self.tabs)
self.status_bar = QStatusBar(self)
self.setStatusBar(self.status_bar)
self.setMenuBar(self.create_dynamic_menu_bar())
def create_dynamic_menu_bar(self) -> QMenuBar:
menu_bar = QMenuBar()
menu_contributions: [MenuContribution] = flat_map(PluginRegistry.execute("get_menu_contributions"))
menu_contributions.append(MenuContribution("file", action=self._action_close(), action_id="close app"))
menu_contributions.append(MenuContribution("settings", action=self._action_highlighter()))
menu_contributions.append(MenuContribution("settings", action=self._action_highlight_search_terms()))
menu_contributions.append(MenuContribution("settings", action=self._action_new_tab()))
menu_contributions.append(MenuContribution("help", action=_action_about()))
known_menus = [
("file", self.tr("&File")),
("settings", self.tr("&Settings")),
("help", self.tr("&Help"))
]
for (menu_id, menu_label) in known_menus:
menu = QMenu(menu_label, self)
mcs: [MenuContribution] = [mc for mc in menu_contributions if mc.menu_id == menu_id]
for menu_contribution in mcs:
print("%s %s" % (menu_id, menu_contribution.action_id))
if menu_contribution.action:
action = self._raction_to_qaction(menu_contribution.action, menu)
menu.addAction(action)
if menu_contribution.menu:
submenu = QMenu(self.tr(menu_contribution.menu.label), menu_bar)
menu_contribution.menu.add_change_listener(
lambda qmenu=submenu, rmenu=menu_contribution.menu: self._rmenu_update(qmenu, rmenu))
self._rmenu_update(submenu, menu_contribution.menu)
menu.addMenu(submenu)
menu_bar.addMenu(menu)
return menu_bar
def _rmenu_update(self, qmenu: QMenu, rmenu: RMenu):
qmenu.clear()
for action in rmenu.actions:
action = self._raction_to_qaction(action, qmenu)
qmenu.addAction(action)
def _raction_to_qaction(self, raction: RAction, qmenu: QMenu) -> QAction:
action = QAction(self.tr(raction.label), qmenu)
if raction.icon_from_theme:
action.setIcon(QIcon.fromTheme(raction.icon_from_theme))
if raction.icon_file:
action.setIcon(QIcon(raction.icon_file))
if raction.shortcut:
action.setShortcut(raction.shortcut)
if raction.action:
action.triggered.connect(raction.action)
if raction.checkable:
action.setCheckable(raction.checkable)
action.setChecked(raction.checked)
return action
def _action_open_file(self) -> RAction:
open_file = RAction("&Open...", action=self._open_file_dialog, shortcut='Ctrl+O',
icon_from_theme="document-open")
return open_file
def _action_close(self) -> RAction:
# Linux: QIcon.fromTheme("exit")
icon = "close" if sys.platform == 'win32' or sys.platform == 'cygwin' else "exit"
close_action = RAction("E&xit", action=self.destruct, shortcut='Ctrl+X', icon_from_theme=icon)
return close_action
def create_menu_bar(self) -> QMenuBar:
menu_bar = QMenuBar()
menu_bar.addMenu(self.file_menu())
menu_bar.addMenu(self.settings_menu())
menu_bar.addMenu(self.help_menu())
return menu_bar
def file_menu(self) -> QMenu:
file_menu = QMenu(self.tr("&File", "name of the file menu"), self)
open_file = self._action_open_file()
close_action = self._action_close()
self._update_recent_files_menu()
file_menu.addAction(open_file)
file_menu.addMenu(self._menu_recent_files)
file_menu.addAction(close_action)
return file_menu
def _action_highlighter(self):
manage = RAction(
"&Highlighter",
action=lambda: HighlightingDialog(self.settings).exec(),
shortcut='Ctrl+H'
)
return manage
def _action_highlight_search_terms(self):
highlight_search_terms = RAction(
"Highlight &Searches",
action=lambda checked: self.settings.set_session("general", "highlight_search_term",
str(checked)) or self.update()
)
highlight_search_terms.set_checkable(True)
highlight_search_terms.set_checked(self.settings.session.getboolean("general", "highlight_search_term"))
return highlight_search_terms
def _action_new_tab(self):
new_tab = RAction("Open Tab on Save As File")
new_tab.set_checkable(True)
new_tab.set_checked(self.settings.session.getboolean("general", "open_tab_on_save_as_file"))
new_tab.set_action(
lambda checked: self.settings.set_session("general", "open_tab_on_save_as_file", str(checked)))
return new_tab
def settings_menu(self) -> QMenu:
result = QMenu(self.tr("&Settings"), self)
result.addAction(self._action_highlighter())
result.addAction(self._action_highlight_search_terms())
result.addAction(self._action_new_tab())
return result
def help_menu(self) -> QMenu:
help_menu = QMenu(self.tr("&Help", "name of the help menu"), self)
help_menu.addAction(_action_about())
return help_menu
def _update_recent_files_menu(self):
self._menu_recent_files.clear()
files = self._get_recent_files()
for file in files:
action = QAction(os.path.basename(file), self)
action.triggered.connect(lambda x, f=file: self.open_file(f))
self._menu_recent_files.addAction(action)
def _open_file_dialog(self) -> None:
current_file = self.tabs.current_file()
directory = os.path.dirname(current_file) if current_file else ''
dialog = QFileDialog(self)
(selected_file, _filter) = dialog.getOpenFileName(
caption=self.tr("Open File"),
directory=directory
)
if selected_file:
self.tabs.create_tab(selected_file)
self._remember_recent_file(selected_file)
def current_file(self) -> Optional[str]:
return self.tabs.current_file()
def open_file(self, file: str) -> None:
self.tabs.create_tab(file)
PluginRegistry.execute("after_open_file", file)
# self._remember_recent_file(file)
# can_open_plugins = PluginRegistry.execute("can_open_file", file)
# if len(can_open_plugins) == 1:
# else:
def _get_recent_files(self) -> [str]:
recent_files = self.settings.session.get('general', 'recent_files', fallback='')
files = recent_files.split(os.pathsep)
if "" in files:
files.remove("")
return files
# def _remember_recent_file(self, file: str):
# files = self._get_recent_files()
# if file in files:
# files.remove(file)
# files.insert(0, file)
# recent_files = os.pathsep.join(files[:10])
# self.settings.set_session('general', 'recent_files', recent_files)
# self._update_recent_files_menu()
def dragEnterEvent(self, e: QDragEnterEvent):
if url_is_file(e.mimeData().text()):
e.accept()
else:
e.ignore()
def dropEvent(self, e):
file = urlutils.url_to_path(e.mimeData().text())
self.open_file(file)
def _restore_window(self):
qsettings = CuteSettings()
geometry_restored = False
geometry = qsettings.value("mainWindow/geometry")
if geometry:
geometry_restored = self.restoreGeometry(geometry)
if not geometry_restored:
self.setGeometry(0, 0, 800, 600)
def closeEvent(self, event):
self.destruct()
def destruct(self):
self.tabs.destruct()
self.close()
SettingsStore.save(self.settings)
CuteSettings().set_value("mainWindow/geometry", self.saveGeometry())

View File

@@ -15,53 +15,74 @@ class PluginRegistry():
modules: [ModuleType] = []
@staticmethod
def register_plugin(name: str, plugin: PluginBase):
def _register_plugin(name: str, plugin: PluginBase):
PluginRegistry.plugins[name] = plugin
@staticmethod
def get_plugins_by_function(function_name: str) -> [PluginBase]:
result = []
for plugin in PluginRegistry.plugins.values():
fun = getattr(plugin, function_name, None)
if callable(fun):
result.append(plugin)
return result
@staticmethod
def load_module(module_name: str) -> ModuleType:
module_name = f"plugins.{module_name}"
module = import_module(module_name)
PluginRegistry.modules.append(module)
return module
# @staticmethod
# def load_module(module_name: str) -> ModuleType:
# module_name = f"raven.plugins.{module_name}"
# module = import_module(module_name)
# PluginRegistry.modules.append(module)
# return module
@staticmethod
def load_plugin(plugin_name: str) -> PluginBase:
module_name = f"plugins.{plugin_name.lower()}"
module_name = f"raven.plugins.{plugin_name.lower()}"
module = import_module(module_name)
if plugin_name in dir(module):
plugin_class = getattr(module, plugin_name)
if isclass(plugin_class) and issubclass(plugin_class, PluginBase):
PluginRegistry.register_plugin(plugin_name, plugin_class())
PluginRegistry._register_plugin(plugin_name, plugin_class())
return plugin_class
raise RuntimeError("plugin %s not found" % plugin_name)
@staticmethod
def get_modules() -> [ModuleType]:
return PluginRegistry.modules.copy()
# @staticmethod
# def get_modules() -> [ModuleType]:
# return PluginRegistry.modules.copy()
@staticmethod
def get_plugins() -> [PluginBase]:
return PluginRegistry.modules.copy()
@staticmethod
def execute(function_name: str, *args):
def executeSingle(function_name: str, *args):
return PluginRegistry._execute(function_name, True, *args)
@staticmethod
def execute(function_name: str, *args) -> []:
return PluginRegistry._execute(function_name, False, *args)
@staticmethod
def _execute(function_name: str, return_first: bool, *args) -> []:
result = []
for plugin in PluginRegistry.plugins.values():
fun = getattr(plugin, function_name, None)
sig = signature(fun)
if callable(fun):
sig = signature(fun)
if len(sig.parameters) != len(args):
raise RuntimeError("method %s.%s has wrong number of arguments. expected %s but was %s " % (
plugin, function_name, len(args), len(sig.parameters)))
fun(args)
plugin, function_name, len(args), len(sig.parameters)))
print("calling %s with args %s" % (fun, args))
if len(args) == 0:
return_value = fun()
elif len(args) == 1:
return_value = fun(args[0])
elif len(args) == 2:
return_value = fun(args[0], args[1])
elif len(args) == 3:
return_value = fun(args[0], args[1], args[2])
elif len(args) == 4:
return_value = fun(args[0], args[1], args[2], args[3])
elif len(args) == 5:
return_value = fun(args[0], args[1], args[2], args[3], args[4])
else:
raise Exception("too many arguments")
if return_first:
return return_value
result.append(return_value)
if return_first:
return None
return result

View File

View File

@@ -0,0 +1,11 @@
from raven.plugins.domain.raction import RAction
from raven.plugins.domain.rmenu import RMenu
class MenuContribution():
def __init__(self, menu_id: str, action: RAction = None, menu: RMenu = None, action_id=None):
super(MenuContribution, self).__init__()
self.menu_id = menu_id
self.action = action
self.menu = menu
self.action_id = action_id

View File

@@ -0,0 +1,37 @@
class RAction():
def __init__(self,
label: str,
action=None,
shortcut: str = None,
icon_from_theme: str = None,
icon_file: str = None,
checkable: bool = False,
checked: bool = False
):
super(RAction, self).__init__()
self.label = label
self.action = action
self.shortcut = shortcut
self.icon_from_theme = icon_from_theme
self.icon_file = icon_file
self.checkable = checkable
self.checked = checked
def set_action(self, action):
self.action = action
def set_icon_from_theme(self, icon_from_theme: str):
self.icon_from_theme = icon_from_theme
def set_icon_file(self, icon_file: str):
self.icon_file = icon_file
def set_shortcut(self, shortcut: str):
self.shortcut = shortcut
def set_checkable(self, checkable: bool):
self.checkable = checkable
def set_checked(self, checked: bool):
self.checked = checked

View File

@@ -0,0 +1,26 @@
from typing import Callable
from raven.plugins.domain.raction import RAction
class RMenu():
def __init__(self, label: str):
super(RMenu, self).__init__()
self.label = label
self.actions = []
self.listeners = []
def add_action(self, action: RAction):
self.actions.append(action)
self._notify()
def clear(self):
self.actions.clear()
self._notify()
def _notify(self):
for listener in self.listeners:
listener()
def add_change_listener(self, listener: Callable[[], None]):
self.listeners.append(listener)

View File

@@ -0,0 +1,84 @@
import os
from typing import Callable
from PyQt6.QtGui import QAction, QIcon
from PyQt6.QtWidgets import QMenu, QFileDialog
from raven.pluginbase import PluginBase
from raven.pluginregistry import PluginRegistry
from raven.plugins.domain.menucontribution import MenuContribution
from raven.plugins.domain.raction import RAction
from raven.plugins.domain.rmenu import RMenu
from settings import Settings
class OpenFilePlugin(PluginBase):
def __init__(self):
super(OpenFilePlugin, self).__init__()
print("init OpenFilePlugin")
def set_settings(self, settings: Settings):
self.settings = settings
def set_translator(self, tr: Callable[[str], str]):
self.tr = tr
def _action_open_file(self) -> RAction:
open_file = RAction(self.tr("&Open..."), self._open_file_dialog, shortcut='Ctrl+O',
icon_from_theme="document-open")
return open_file
def _sub_menu_recent_files(self) -> RMenu:
self._menu_recent_files = RMenu("Open &Recent")
self._update_recent_files_menu()
return self._menu_recent_files
def get_menu_contributions(self) -> [MenuContribution]:
return [
MenuContribution("file", action=self._action_open_file(), action_id="open file"),
MenuContribution("file", menu=self._sub_menu_recent_files(), action_id="recent files menu"),
]
def _open_file_dialog(self) -> None:
current_file = PluginRegistry.executeSingle("current_file")
directory = os.path.dirname(current_file) if current_file else ''
dialog = QFileDialog()
(selected_file, _filter) = dialog.getOpenFileName(
caption=self.tr("Open File"),
directory=directory
)
if selected_file:
self._open_file(selected_file)
def _open_file(self, selected_file: str):
PluginRegistry.executeSingle("create_tab", selected_file)
self._remember_recent_file(selected_file)
def _get_recent_files(self) -> [str]:
recent_files = self.settings.session.get('general', 'recent_files', fallback='')
print(recent_files)
files = recent_files.split(os.pathsep)
if "" in files:
files.remove("")
return files
def _update_recent_files_menu(self):
self._menu_recent_files.clear()
files = self._get_recent_files()
for file in files:
action = RAction(os.path.basename(file))
action.set_action(lambda x, f=file: self._open_file(f))
self._menu_recent_files.add_action(action)
def _remember_recent_file(self, file: str):
files = self._get_recent_files()
if file in files:
files.remove(file)
files.insert(0, file)
recent_files = os.pathsep.join(files[:10])
self.settings.set_session('general', 'recent_files', recent_files)
self._update_recent_files_menu()
def after_open_file(self, file: str):
self._remember_recent_file(file)

View File

@@ -0,0 +1,21 @@
from typing import Optional
from raven.mainwindow import MainWindow
from raven.pluginbase import PluginBase
class RavenLogPlugin(PluginBase):
def __init__(self):
super(RavenLogPlugin, self).__init__()
self.main_window = None
def create_main_window(self):
if not self.main_window:
self.main_window = MainWindow()
return self.main_window
def current_file(self) -> Optional[str]:
return self.main_window.current_file()
def create_tab(self, file: str):
self.main_window.tabs.create_tab(file)

View File

@@ -62,8 +62,8 @@ def set_window_icon(app: QApplication):
if __name__ == "__main__":
filterplugin = PluginRegistry.load_plugin("FilterPlugin")
logfileviewerplugin = PluginRegistry.load_plugin("LogFileViewerPlugin")
PluginRegistry.load_plugin("FilterPlugin")
PluginRegistry.load_plugin("LogFileViewerPlugin")
PluginRegistry.execute("say_hello", "World")