mirror of
https://github.com/gnuradio/gnuradio-companion.git
synced 2025-12-10 00:42:30 -06:00
312 lines
12 KiB
Python
312 lines
12 KiB
Python
import logging
|
|
import os
|
|
import traceback
|
|
|
|
from qtpy import uic
|
|
from qtpy.QtCore import QObject, Signal, Slot, QRunnable, QVariant, Qt
|
|
from qtpy.QtGui import QPixmap, QStandardItem, QStandardItemModel
|
|
from qtpy.QtWidgets import QDialog, QListWidgetItem, QTreeWidgetItem, QWidget, QVBoxLayout
|
|
|
|
|
|
from ...core.cache import Cache
|
|
from .. import base, Constants
|
|
from ..properties import Paths
|
|
|
|
# Logging
|
|
log = logging.getLogger(f"grc.application.{__name__}")
|
|
|
|
|
|
class WorkerSignals(QObject):
|
|
error = Signal(tuple)
|
|
result = Signal(object)
|
|
progress = Signal(tuple)
|
|
|
|
|
|
class Worker(QRunnable):
|
|
"""
|
|
This is the Worker that will gather/parse examples as a background task
|
|
"""
|
|
def __init__(self, fn, *args, **kwargs):
|
|
super(Worker, self).__init__()
|
|
|
|
self.fn = fn
|
|
self.args = args
|
|
self.kwargs = kwargs
|
|
self.signals = WorkerSignals()
|
|
|
|
self.kwargs['progress_callback'] = self.signals.progress
|
|
|
|
@Slot()
|
|
def run(self):
|
|
try:
|
|
result = self.fn(*self.args, **self.kwargs)
|
|
except:
|
|
print("Error in background task:")
|
|
traceback.print_exc()
|
|
else:
|
|
self.signals.result.emit(result)
|
|
|
|
|
|
class ExampleBrowserDialog(QDialog):
|
|
def __init__(self, browser):
|
|
super(ExampleBrowserDialog, self).__init__()
|
|
|
|
self.setMinimumSize(600, 400)
|
|
self.setModal(True)
|
|
|
|
self.setWindowTitle("GRC Examples")
|
|
self.browser = browser
|
|
self.layout = QVBoxLayout()
|
|
self.setLayout(self.layout)
|
|
self.layout.addWidget(browser)
|
|
self.browser.connect_dialog(self)
|
|
|
|
|
|
class ExampleBrowser(QWidget, base.Component):
|
|
file_to_open = Signal(str)
|
|
data_role = Qt.UserRole
|
|
|
|
lang_dict = {
|
|
'python': 'Python',
|
|
'cpp': 'C++',
|
|
}
|
|
|
|
gen_opts_dict = {
|
|
'no_gui': 'No GUI',
|
|
'qt_gui': 'Qt GUI',
|
|
'bokeh_gui': 'Bokeh GUI',
|
|
'hb': 'Hier block ',
|
|
'hb_qt_gui': 'Hier block (Qt GUI)'
|
|
}
|
|
|
|
def __init__(self):
|
|
super(ExampleBrowser, self).__init__()
|
|
uic.loadUi(Paths.RESOURCES + "/example_browser_widget.ui", self)
|
|
self.library = None
|
|
self.dialog = None
|
|
|
|
self.tree_widget.setHeaderHidden(True)
|
|
self.tree_widget.clicked.connect(self.handle_clicked)
|
|
|
|
self.cpp_qt_fg = QPixmap(Paths.RESOURCES + "/cpp_qt_fg.png")
|
|
self.cpp_cmd_fg = QPixmap(Paths.RESOURCES + "/cpp_cmd_fg.png")
|
|
self.py_qt_fg = QPixmap(Paths.RESOURCES + "/py_qt_fg.png")
|
|
self.py_cmd_fg = QPixmap(Paths.RESOURCES + "/py_cmd_fg.png")
|
|
|
|
self.examples_dict = self.platform.examples_dict
|
|
self.dir_items = {}
|
|
|
|
self.tree_widget.currentItemChanged.connect(self.populate_preview)
|
|
self.tree_widget.itemDoubleClicked.connect(self.open_file)
|
|
self.open_button.clicked.connect(self.open_file)
|
|
|
|
def set_library(self, library):
|
|
self.library = library
|
|
|
|
def handle_clicked(self):
|
|
if self.tree_widget.isExpanded(self.tree_widget.currentIndex()):
|
|
self.tree_widget.collapse(self.tree_widget.currentIndex())
|
|
else:
|
|
self.tree_widget.expand(self.tree_widget.currentIndex())
|
|
|
|
def connect_dialog(self, dialog: QDialog):
|
|
if self.dialog:
|
|
pass # disconnect?
|
|
|
|
self.dialog = dialog
|
|
if isinstance(dialog, ExampleBrowserDialog):
|
|
self.close_button.setHidden(False)
|
|
self.close_button.clicked.connect(dialog.reject)
|
|
self.open_button.clicked.connect(self.done)
|
|
self.tree_widget.itemDoubleClicked.connect(self.done)
|
|
else:
|
|
raise Exception
|
|
|
|
def done(self, _=None):
|
|
self.dialog.done(0)
|
|
|
|
def populate(self, examples_dict):
|
|
self.examples_dict = examples_dict
|
|
self.tree_widget.clear()
|
|
|
|
for path, examples in examples_dict.items():
|
|
for ex in examples:
|
|
rel_path = os.path.relpath(os.path.dirname(ex['path']), path)
|
|
split_rel_path = os.path.normpath(rel_path).split(os.path.sep)
|
|
parent_path = "/".join(split_rel_path[0:-1])
|
|
if rel_path not in self.dir_items:
|
|
dir_ = None
|
|
if parent_path:
|
|
try:
|
|
dir_ = QTreeWidgetItem(self.dir_items[parent_path])
|
|
except KeyError:
|
|
i = 0
|
|
while i <= len(split_rel_path):
|
|
partial_path = "/".join(split_rel_path[0:i+1])
|
|
split_partial_path = os.path.normpath(partial_path).split(os.path.sep)
|
|
if not partial_path in self.dir_items:
|
|
if i == 0: # Top level
|
|
dir_ = QTreeWidgetItem(self.tree_widget)
|
|
dir_.setText(0, partial_path)
|
|
self.dir_items[partial_path] = dir_
|
|
else:
|
|
dir_ = QTreeWidgetItem(self.dir_items["/".join(split_partial_path[:-1])])
|
|
dir_.setText(0, split_partial_path[-1])
|
|
self.dir_items[partial_path] = dir_
|
|
i += 1
|
|
else:
|
|
dir_ = QTreeWidgetItem(self.tree_widget)
|
|
dir_.setText(0, split_rel_path[-1])
|
|
self.dir_items[rel_path] = dir_
|
|
item = QTreeWidgetItem(self.dir_items[rel_path])
|
|
item.setText(0, ex["title"] if ex["title"] else ex["name"])
|
|
item.setData(0, self.data_role, QVariant(ex))
|
|
|
|
def reset_preview(self):
|
|
self.title_label.setText(f"<b>Title:</b> ")
|
|
self.author_label.setText(f"<b>Author:</b> ")
|
|
self.language_label.setText(f"<b>Output language:</b> ")
|
|
self.gen_opts_label.setText(f"<b>Type:</b> ")
|
|
self.desc_label.setText('')
|
|
self.image_label.setPixmap(QPixmap())
|
|
|
|
def populate_preview(self):
|
|
ex = self.tree_widget.currentItem().data(0, self.data_role)
|
|
|
|
self.title_label.setText(f"<b>Title:</b> {ex['title'] if ex else ''}")
|
|
self.author_label.setText(f"<b>Author:</b> {ex['author'] if ex else ''}")
|
|
try:
|
|
self.language_label.setText(f"<b>Output language:</b> {self.lang_dict[ex['output_language']] if ex else ''}")
|
|
self.gen_opts_label.setText(f"<b>Type:</b> {self.gen_opts_dict[ex['generate_options']] if ex else ''}")
|
|
except KeyError:
|
|
self.language_label.setText(f"<b>Output language:</b> ")
|
|
self.gen_opts_label.setText(f"<b>Type:</b> ")
|
|
self.desc_label.setText(ex["desc"] if ex else '')
|
|
|
|
if ex:
|
|
if ex["output_language"] == "python":
|
|
if ex["generate_options"] == "qt_gui":
|
|
self.image_label.setPixmap(self.py_qt_fg)
|
|
else:
|
|
self.image_label.setPixmap(self.py_cmd_fg)
|
|
else:
|
|
if ex["generate_options"] == "qt_gui":
|
|
self.image_label.setPixmap(self.cpp_qt_fg)
|
|
else:
|
|
self.image_label.setPixmap(self.cpp_cmd_fg)
|
|
else:
|
|
self.image_label.setPixmap(QPixmap())
|
|
|
|
|
|
def open_file(self):
|
|
ex = self.tree_widget.currentItem().data(0, self.data_role)
|
|
self.file_to_open.emit(ex["path"])
|
|
|
|
def filter_(self, key: str):
|
|
"""
|
|
Only display examples that contain a specific block. (Hide the others)
|
|
|
|
Parameters:
|
|
key: The key of the block to search for
|
|
"""
|
|
found = False
|
|
ex_paths = self.library.get_examples(key)
|
|
for i in range(self.tree_widget.topLevelItemCount()):
|
|
top = self.tree_widget.topLevelItem(i)
|
|
if self.show_selective(top, ex_paths):
|
|
found = True
|
|
return found
|
|
|
|
|
|
def show_selective(self, item, path):
|
|
item.setHidden(True)
|
|
if item.childCount(): # is a directory
|
|
for i in range(item.childCount()):
|
|
if self.show_selective(item.child(i), path):
|
|
item.setHidden(False)
|
|
return not item.isHidden()
|
|
else: # is an example
|
|
ex = item.data(0, self.data_role)
|
|
if ex['path'] in path:
|
|
item.setHidden(False)
|
|
return True
|
|
else:
|
|
return False
|
|
|
|
def show_all(self, item):
|
|
item.setHidden(False)
|
|
for i in range(item.childCount()):
|
|
self.show_all(item.child(i))
|
|
|
|
def reset(self):
|
|
"""Reset the filter, collapse all."""
|
|
self.tree_widget.collapseAll()
|
|
self.reset_preview()
|
|
|
|
for i in range(self.tree_widget.topLevelItemCount()):
|
|
top = self.tree_widget.topLevelItem(i)
|
|
self.show_all(top)
|
|
|
|
|
|
def find_examples(self, progress_callback, ext="grc"):
|
|
"""Iterate through the example flowgraph directories and parse them."""
|
|
examples_dict = {}
|
|
with Cache(Constants.EXAMPLE_CACHE_FILE, log=False) as cache:
|
|
for entry in self.platform.config.example_paths:
|
|
if entry == '':
|
|
log.error("Empty example path!")
|
|
break
|
|
examples_dict[entry] = []
|
|
if os.path.isdir(entry):
|
|
subdirs = 0
|
|
current_subdir = 0
|
|
for dirpath, dirnames, filenames in os.walk(entry):
|
|
subdirs += 1 # Loop through once to see how many there are
|
|
for dirpath, dirnames, filenames in os.walk(entry):
|
|
dirnames.sort()
|
|
current_subdir += 1
|
|
progress_callback.emit((int(100 * current_subdir / subdirs), "Indexing examples"))
|
|
for filename in sorted(filter(lambda f: f.endswith('.' + ext), filenames)):
|
|
file_path = os.path.join(dirpath, filename)
|
|
try:
|
|
data = cache.get_or_load(file_path)
|
|
example = {}
|
|
example["name"] = os.path.basename(file_path)
|
|
example["generate_options"] = data["options"]["parameters"].get("generate_options") or "no_gui"
|
|
example["output_language"] = data["options"]["parameters"].get("output_language") or "python"
|
|
example["example_filter"] = data["metadata"].get("example_filter") or []
|
|
example["title"] = data["options"]["parameters"]["title"] or ""
|
|
example["desc"] = data["options"]["parameters"]["description"] or ""
|
|
example["author"] = data["options"]["parameters"]["author"] or ""
|
|
example["path"] = file_path
|
|
example["module"] = os.path.dirname(file_path).replace(entry, "")
|
|
if example["module"].startswith("/"):
|
|
example["module"] = example["module"][1:]
|
|
|
|
example["blocks"] = set()
|
|
for block in data["blocks"]:
|
|
example["blocks"].add(block["id"])
|
|
examples_dict[entry].append(example)
|
|
except Exception:
|
|
continue
|
|
|
|
examples_w_block: dict[str, set[str]] = {}
|
|
designated_examples_w_block: dict[str, set[str]] = {}
|
|
for path, examples in examples_dict.items():
|
|
for example in examples:
|
|
if example["example_filter"]:
|
|
for block in example["example_filter"]:
|
|
try:
|
|
designated_examples_w_block[block].append(example["path"])
|
|
except KeyError:
|
|
designated_examples_w_block[block] = [example["path"]]
|
|
continue
|
|
else:
|
|
for block in example["blocks"]:
|
|
try:
|
|
examples_w_block[block].append(example["path"])
|
|
except KeyError:
|
|
examples_w_block[block] = [example["path"]]
|
|
|
|
return (examples_dict, examples_w_block, designated_examples_w_block)
|