I want redesign this docker so that I can set favorites and also learn how to load in these vector thumbnails.
No reason why you can’t, all you need is make a docker with a QListView/QListWidget and addShapesFromSvg().
You can also just make your own symbol library too if you don’t want to make an entire plugin.
If you’re capable of writing a Python plugin then you’ll have no problem creating a vector/Symbol Library item because it’s just structural editing/combining of group/shape .svg files, ideally using a syntax highlighting editor.
Please make a new topic in the Support and Advice: Bundles and resources help category for this if it’s what you want to do.
Just for fun, vector symbols library (not completed)
Hope that this gives some ideas for your plugin.
tested: on Kubuntu Krita 5.2.11
file tree structure
pykrita/
vector_symbols_library_plugin/
__init__.py
vector_symbols_library_docker.py
vector_symbols_library_plugin.desktop
vector_symbols_library_plugin/__init__.py
PLUGIN_NAME = __name__
PLUGIN_NAME_SHORT = 'VSLP' # (V)ector (S)ymbols (L)ibrary (P)lugin
PLUGIN_VERSION = '0.1.0'
def register():
from .vector_symbols_library_docker import VectorSymbolsLibraryDocker
VectorSymbolsLibraryDocker.register()
register()
vector_symbols_library_plugin/vector_symbols_library_docker.py
from pathlib import Path
from krita import (
Krita, DockWidget, DockWidgetFactory, DockWidgetFactoryBase)
from vector_symbols_library_plugin import (
PLUGIN_NAME, PLUGIN_NAME_SHORT, PLUGIN_VERSION)
# maybe use Qt.py -> https://github.com/mottosso/Qt.py
try:
from PyQt6.QtCore import (
Qt,
pyqtSlot as QSlot,
pyqtSignal as QSignal,
pyqtProperty as QProperty,
QRectF, QSize)
from PyQt6.QtGui import (
QAction, QImage, QColor, QPainter)
from PyQt6.QtWidgets import (
QWidget, QLineEdit, QListView, QVBoxLayout,
QToolBar, QSizePolicy, QStyledItemDelegate,
QStyleOptionViewItem)
from PyQt6.QtSql import (
QSqlDatabase, QSqlTableModel, QSqlQuery)
from PyQt6.QtSvg import (
QSvgRenderer,)
except ImportError:
from PyQt5.QtCore import (
Qt,
pyqtSlot as QSlot,
pyqtSignal as QSignal,
pyqtProperty as QProperty,
QRectF, QSize)
from PyQt5.QtGui import (
QImage, QColor, QPainter)
from PyQt5.QtWidgets import (
QWidget, QLineEdit, QListView, QVBoxLayout,
QToolBar, QSizePolicy, QStyledItemDelegate,
QStyleOptionViewItem, QAction)
from PyQt5.QtSql import (
QSqlDatabase, QSqlTableModel, QSqlQuery)
from PyQt5.QtSvg import (
QSvgRenderer,)
_app = Krita.instance()
class VectorSymbolsLibraryDocker(DockWidget):
# Future:
# - tags filtering, ie. tag "favorite"
# - add tags QCompleter to Content._tags_filter
# - fix bug in svg scale, maybe document resolution affects svg?
# - keep thumbnail aspect ratio
# - drag and drop selected to canvas
# - RMB context menu
# - database path & multiple databases at same time
# - add edit tags button to toolbar
# - faster tooltip popup (show tags of an item)
def __init__(self):
super().__init__()
self.setWindowTitle('Vector Symbols Library')
self.setWidget(Content())
def canvasChanged(self, canvas):
pass
@classmethod
def register(cls):
factory = DockWidgetFactory(
f'{PLUGIN_NAME}:vector_symbols_library_docker',
DockWidgetFactoryBase.DockRight,
cls)
_app.addDockWidgetFactory(factory)
class Content(QWidget):
_connection_name = 'vector_symbols_library'
_table_name = 'vector_symbols_library'
_db_path = str(Path(__file__).with_name('vector_symbols_library.sqlite3'))
def __init__(self, **kwargs):
super().__init__(**kwargs)
self._init_models()
self._init_ui()
self._connect_signals()
def _init_models(self):
# open existing / create new database
db = QSqlDatabase.addDatabase('QSQLITE', connectionName=self._connection_name)
db.setDatabaseName(self._db_path)
if not db.open():
raise RuntimeError(f'Failed to open {self._db_path}')
self._model = model = VectorSymbolsLibraryModel(db=db, parent=self)
if not QSqlQuery(db).exec(
f'CREATE TABLE IF NOT EXISTS {self._table_name} ('
'ID INTEGER PRIMARY KEY, '
'tags TEXT, '
'svg TEXT);'):
print(f'Failed to create table! {self._table_name = }')
if model.submitAll():
model.database().commit()
model.setTable(self._table_name)
model.setEditStrategy(QSqlTableModel.EditStrategy.OnManualSubmit)
model.select()
for i, column_name in enumerate(('ID', 'tags', 'svg')):
model.setHeaderData(i, Qt.Orientation.Horizontal, column_name)
def _init_ui(self):
layout = QVBoxLayout()
layout.setSpacing(3)
self.setLayout(layout)
self._tags_filter = QLineEdit( # noqa
placeholderText='filter using tags ...',
clearButtonEnabled=True)
layout.addWidget(self._tags_filter)
self._view = QListView( # noqa PyCharm activated stobid mode!
alternatingRowColors=True, # noqa
flow=QListView.Flow.LeftToRight, # noqa
isWrapping=True, # noqa
resizeMode=QListView.ResizeMode.Adjust, # noqa
selectionRectVisible=True, # noqa
spacing=3, # noqa
selectionMode=QListView.SelectionMode.ExtendedSelection) # noqa
self._view.setModel(self._model)
self._view.setItemDelegate(ItemDelegate(parent=self._view))
layout.addWidget(self._view, stretch=100)
self._tool_bar = QToolBar()
self._tool_bar.layout().setContentsMargins(0, 0, 0, 0) # noqa
self._add_to_doc_action = QAction(text='Add to Doc', parent=self)
self._tool_bar.addAction(self._add_to_doc_action)
self._add_to_lib_action = QAction(text='Add to Lib', parent=self)
self._tool_bar.addAction(self._add_to_lib_action)
self._tool_bar.addWidget(QWidget(sizePolicy=QSizePolicy( # noqa
QSizePolicy.Policy.Expanding,
QSizePolicy.Policy.Expanding)))
self._remove_selected_action = QAction(text='Trash', parent=self)
self._tool_bar.addAction(self._remove_selected_action)
layout.addWidget(self._tool_bar)
def _connect_signals(self):
self._add_to_doc_action.triggered.connect(self._on_add_to_doc_triggered)
self._add_to_lib_action.triggered.connect(self._on_add_to_lib_triggered)
self._remove_selected_action.triggered.connect(self._on_remove_selected_triggered)
self._tags_filter.editingFinished.connect(self._on_tags_filter_edited)
def _on_add_to_doc_triggered(self, checked=None):
doc = _app.activeDocument()
layer = doc.activeNode()
if layer.type() != 'vectorlayer':
return # not a vector layer
for index in self._view.selectedIndexes():
layer.addShapesFromSvg(index.data(role=Qt.ItemDataRole.UserRole))
# ToDo: bring to front + select all added shapes
def _on_add_to_lib_triggered(self, checked=None):
doc = _app.activeDocument()
layer = doc.activeNode()
if layer.type() != 'vectorlayer':
return # not a vector layer
model = self._model
for shape in layer.shapes():
if not shape.isSelected():
continue # not selected, skip
record = model.record()
# ID PRIMARY KEY is automatically set by SQLite
record.setValue('tags', 'untagged favorite best red') # multiple tags, just as an example
record.setValue('svg', f'<svg>{shape.toSvg()}</svg>')
model.insertRecord(-1, record)
if model.submitAll():
model.database().commit()
def _on_remove_selected_triggered(self, checked=None):
pass # ToDo: remove row from model and submit+commit to database
def _on_tags_filter_edited(self):
pass # ToDo: call VectorSymbolsLibraryModel.setFilter()
class VectorSymbolsLibraryModel(QSqlTableModel):
# model that remaps roles to columns
# also thumbnail is rendered from svg and cached, ToDo: push thumbnail rendering to thread?
#
# Future:
# - editable thumbnail size + clear cached
# - filter using tags
def __init__(self, **kwargs):
self._cached_image = dict()
self._thumbnail_size = QSize(64, 64)
super().__init__(**kwargs)
def get_cached_image(self, index):
key = super().data(index.siblingAtColumn(0), Qt.ItemDataRole.EditRole)
image = self._cached_image.get(key)
if not image:
image = QImage(self._thumbnail_size, QImage.Format.Format_RGBA8888)
image.fill(QColor(0, 0, 0, 0))
painter = QPainter(image)
svg_bytes = super().data(index.siblingAtColumn(2), Qt.ItemDataRole.EditRole).encode('ASCII')
QSvgRenderer(svg_bytes).render(painter, QRectF(image.rect()))
painter.end()
self._cached_image[key] = image
return image
def data(self, index, role=Qt.ItemDataRole.DisplayRole):
if index.column() == 0:
match role:
case Qt.ItemDataRole.EditRole | Qt.ItemDataRole.DisplayRole | Qt.ItemDataRole.ToolTipRole:
return super().data(index.siblingAtColumn(1), Qt.ItemDataRole.EditRole)
case Qt.ItemDataRole.DecorationRole:
return self.get_cached_image(index)
case Qt.ItemDataRole.UserRole:
return super().data(index.siblingAtColumn(2), Qt.ItemDataRole.EditRole)
return super().data(index, role)
def setData(self, index, value, role=Qt.ItemDataRole.EditRole):
if index.column() == 0:
match role:
case Qt.ItemDataRole.EditRole | Qt.ItemDataRole.DisplayRole:
return super().setData(index.siblingAtColumn(1), value, Qt.ItemDataRole.EditRole)
case Qt.ItemDataRole.UserRole:
key = super().data(index.siblingAtColumn(0), Qt.ItemDataRole.EditRole)
self._cached_pixmap.pop(key, None) # remove cached thumbnail
return super().setData(index.siblingAtColumn(2), value, Qt.ItemDataRole.EditRole)
return super().setData(index, value, role)
class ItemDelegate(QStyledItemDelegate):
# Future: open better tags editor
def initStyleOption(self, opt, index):
super().initStyleOption(opt, index)
opt.features &= ~QStyleOptionViewItem.ViewItemFeature.HasDisplay # exclude item "text"
vector_symbols_library_plugin.desktop
[Desktop Entry]
Type=Service
ServiceTypes=Krita/PythonPlugin
X-KDE-Library=vector_symbols_library_plugin
X-Python-2-Compatible=false
X-Krita-Manual=readme.md
Name=Vector Symbols Library Plugin
Comment=Vector Symbols Library Plugin
/AkiR

