Skip to content

Commit e171791

Browse files
committed
1.add signal to auto switch docker/dialog; 2.keep one instance; 3.add keyboard shortcut;
1 parent cbf4055 commit e171791

File tree

6 files changed

+290
-21
lines changed

6 files changed

+290
-21
lines changed
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
from krita import *
2+
from PyQt5.QtWidgets import QDialog, QHBoxLayout
3+
from .PluginDevToolsWidget import *
4+
5+
class PluginDevToolsDialog(QDialog):
6+
signal_closed = pyqtSignal()
7+
def __init__(self, parent=None):
8+
super().__init__(parent)
9+
self.__layout = QHBoxLayout()
10+
self.setLayout(self.__layout)
11+
self.setWindowTitle('Plugin Developer Tools')
12+
# Can keep a standalone window after setParent with qwin
13+
self.setWindowFlag(Qt.WindowType.Window, True)
14+
15+
16+
def closeEvent(self, event: QtGui.QCloseEvent):
17+
self.signal_closed.emit()
18+
return super().closeEvent(event)
19+
20+

plugindevtools/PluginDevTools/PluginDevToolsDocker.py

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,31 @@
55
DOCKER_TITLE = 'Plugin Developer Tools'
66

77
class PluginDevToolsDocker(DockWidget):
8+
signal_leaveFloating = pyqtSignal()
9+
signal_manualOpenDocker = pyqtSignal()
810
def __init__(self):
911
super().__init__()
1012
self.setWindowTitle(DOCKER_TITLE)
11-
self.mainWidget = PluginDevToolsWidget()
12-
self.setWidget(self.mainWidget)
1313

1414
def canvasChanged(self, canvas):
1515
pass
1616

17+
def showEvent(self, event: QtGui.QShowEvent) -> None:
18+
print('PluginDevToolsDocker showEvent')
19+
print(' sender= ', self.sender())
20+
if isinstance(self.sender(), QAction):
21+
self.signal_manualOpenDocker.emit()
22+
return super().showEvent(event)
23+
# Failed to catch mousePressEvent/mouseReleaseEvent when moving docker
24+
# Failed to catch clicked/toggled/pressed/released signal when click the docker float button
25+
# Use showEvent instead
26+
if (QGuiApplication.mouseButtons() & Qt.MouseButton.LeftButton):
27+
# Do not switch immediately when dragging the docker to move
28+
return super().showEvent(event)
29+
30+
# LeftButton is released and the docker is not docked
31+
if self.isFloating():
32+
self.signal_leaveFloating.emit()
33+
return super().showEvent(event)
34+
35+
Lines changed: 51 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,64 @@
11
from krita import *
2-
from PyQt5.QtWidgets import QDialog, QHBoxLayout
3-
from .PluginDevToolsWidget import *
42

5-
class PluginDevToolsExtension(Extension):
6-
def __init__(self, parent):
7-
super().__init__(parent)
3+
## TODO: Planed to add some functions in future.
4+
5+
#class PluginDevToolsExtension(Extension):
6+
# def __init__(self, parent):
7+
# super().__init__(parent)
8+
#
9+
# # Krita.instance() exists, so do any setup work
10+
# def setup(self):
11+
# pass
12+
#
13+
# # called after setup(self)
14+
# def createActions(self, window):
15+
# pass
816

917

18+
# This is a test extension
19+
import inspect
20+
class PluginDevToolsTestExtension(Extension):
21+
def __init__(self, parent):
22+
super().__init__(parent)
23+
1024
def setup(self):
1125
pass
1226

13-
def initialize(self):
14-
self.dialog = QDialog()
27+
def createActions(self, window: Window):
28+
action = window.createAction('PluginDevTools', 'PluginDevTools', 'tools/scripts/PluginDevTools')
29+
self.menu = QMenu('PluginDevTools', window.qwindow())
30+
action.setMenu(self.menu)
31+
# Add some fixed Action here:
32+
#action = window.createAction('actionID', 'actionDisplayName', 'tools/scripts/PluginDevTools')
33+
#self.menu.addAction(action)
34+
#action.triggered.connect(someConnectObject)
35+
1536

16-
self.dialogLayout = QHBoxLayout()
17-
self.dialog.setLayout(self.dialogLayout)
37+
def dynamicAddEntry(self, *args):
38+
# Only for debug
39+
# How to use in console:
40+
#debugentry = next((w for w in Krita.instance().extensions() if str(type(w)).__contains__('PluginDevToolsDebugEntry')), None)
41+
#print(vars(debugentry))
42+
# then use debugentry.yourVariable to access yourVariable
43+
frame = inspect.currentframe()
44+
frame = inspect.getouterframes(frame)[1]
45+
string = inspect.getframeinfo(frame[0]).code_context
46+
if string is not None:
47+
string = string[0].strip()
48+
else:
49+
return
1850

19-
self.dialogLayoutContent = PluginDevToolsWidget()
20-
self.dialogLayout.addWidget(self.dialogLayoutContent)
51+
args_name = string[string.find('(') + 1:-1].replace(' ', '').split(',')
52+
53+
for name, value in zip(args_name, args):
54+
setattr(self, name, value)
2155

22-
self.dialog.setWindowTitle('Plugin Developer Tools')
23-
self.dialog.show()
56+
def dynamicCreateAction(self, connectObject, window: Window, id_text: str, displayName: str = str(), addToMenu=False) ->QAction:
57+
action = window.createAction(id_text, displayName)
58+
action.triggered.connect(connectObject)
59+
if addToMenu:
60+
self.menu.addAction(action)
2461

25-
def createActions(self, window):
26-
action = window.createAction("pluginDevTools", "Plugin Developer Tools", "tools/scripts")
27-
action.triggered.connect(self.initialize)
62+
return action
2863

2964

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
from krita import *
2+
from PyQt5.QtCore import *
3+
from .PluginDevToolsWidget import *
4+
from .PluginDevToolsDocker import *
5+
from .PluginDevToolsDialog import *
6+
7+
8+
class PluginDevToolsStatusManager():
9+
# shared with all instances
10+
mutex = QMutex()
11+
12+
def __init__(self, name=str()):
13+
self.__objectname = name
14+
self.docker: PluginDevToolsDocker
15+
self.dialog: PluginDevToolsDialog
16+
self.centralwidget: PluginDevToolsWidget
17+
18+
19+
def objectName(self):
20+
# Only for debug
21+
return self.__objectname
22+
23+
24+
def setDocker(self, docker: PluginDevToolsDocker):
25+
self.docker = docker
26+
27+
28+
def setDialog(self, dialog: PluginDevToolsDialog):
29+
self.dialog = dialog
30+
31+
32+
def setCentralWidget(self, centralwidget: PluginDevToolsWidget):
33+
self.centralwidget = centralwidget
34+
35+
36+
def setFirstAfterStart(self):
37+
# Initialize the first status after start Krita
38+
if self.docker.isFloating():
39+
self.applyDialogMode('initiallize')
40+
elif self.docker.isVisible():
41+
self.applyDockerMode('initiallize')
42+
else:
43+
self.applyHideMode('initiallize')
44+
45+
46+
def applyHideMode(self, senderName=str()):
47+
if type(self).mutex.tryLock() == False:
48+
#print('skipped applyHideMode')
49+
#print(' mutex.tryLock()= ', False)
50+
#print(' sender= ', senderName)
51+
#print('')
52+
return
53+
self.docker.setFloating(False)
54+
self.docker.setWidget(self.centralwidget)
55+
self.docker.close()
56+
self.dialog.close()
57+
type(self).mutex.unlock()
58+
59+
60+
def applyDockerMode(self, senderName=str()):
61+
if type(self).mutex.tryLock() == False:
62+
#print('skipped applyDockerMode')
63+
#print(' mutex.tryLock()= ', False)
64+
#print(' sender= ', senderName)
65+
#print('')
66+
return
67+
self.docker.setFloating(False)
68+
self.docker.setWidget(self.centralwidget)
69+
self.docker.show()
70+
self.dialog.close()
71+
type(self).mutex.unlock()
72+
73+
74+
def applyDialogMode(self, senderName=str()):
75+
if type(self).mutex.tryLock() == False:
76+
#print('skipped applyDialogMode')
77+
#print(' mutex.tryLock()= ', False)
78+
#print(' sender= ', senderName)
79+
#print('')
80+
return
81+
self.docker.setFloating(False)
82+
self.docker.close()
83+
self.dialog.layout().addWidget(self.centralwidget)
84+
self.dialog.show()
85+
self.dialog.activateWindow()
86+
type(self).mutex.unlock()
87+
88+
Lines changed: 83 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,95 @@
11
from krita import *
22
from .PluginDevToolsExtension import *
33
from .PluginDevToolsDocker import *
4+
from .PluginDevToolsWidget import *
5+
from .PluginDevToolsDialog import *
6+
from .PluginDevToolsStatusManager import *
47

58

69
app = Krita.instance()
710

8-
# Add to extension list
9-
extension = PluginDevToolsExtension(parent=app) #instantiate your class
10-
app.addExtension(extension)
11+
## TODO: Planed to add some functions in future.
12+
## Add to extension list
13+
#extension = PluginDevToolsExtension(parent=app) #instantiate your class
14+
#app.addExtension(extension)
15+
16+
testExtension = PluginDevToolsTestExtension(parent=app)
17+
app.addExtension(testExtension)
1118

1219
# Add to docker list
1320
DOCKER_ID = 'pluginDevToolsDocker'
1421
dock_widget_factory = DockWidgetFactory(DOCKER_ID, DockWidgetFactoryBase.DockBottom, PluginDevToolsDocker)
1522
app.addDockWidgetFactory(dock_widget_factory)
23+
24+
statusManager = PluginDevToolsStatusManager('statusManager')
25+
26+
def retrieveDocker(docker_id: str)->PluginDevToolsDocker:
27+
# Only retrieve docker instance after the main window was completely created
28+
try:
29+
Krita.instance().activeWindow().qwindow()
30+
except:
31+
print('main window not created at this moment')
32+
return PluginDevToolsDocker()
33+
34+
for d in app.dockers():
35+
if d.objectName() == docker_id:
36+
if isinstance(d, PluginDevToolsDocker):
37+
return d
38+
print("cannot find docker: {docker_id}")
39+
exit(1)
40+
41+
42+
def setup():
43+
global statusManager
44+
45+
qwin = Krita.instance().activeWindow().qwindow()
46+
47+
48+
docker = retrieveDocker(DOCKER_ID)
49+
statusManager.setDocker(docker)
50+
51+
# Prepare a standalone window
52+
dialog = PluginDevToolsDialog()
53+
statusManager.setDialog(dialog)
54+
55+
# Prepare content widget. This widget will be added into docker or dialog. Only keep one instance.
56+
centralwidget = PluginDevToolsWidget()
57+
statusManager.setCentralWidget(centralwidget)
58+
59+
# Set dialog's parent to qwindow, when close qwindow, dialog will be closed as well
60+
statusManager.dialog.setParent(qwin)
61+
# Reset windowflag to keep as a standalone window
62+
# Should always reset windowflag after setParent
63+
statusManager.dialog.setWindowFlag(Qt.WindowType.Window, True)
64+
65+
statusManager.setFirstAfterStart()
66+
67+
# Status route: modeAllHide <--> modeDocker <--> modeDialog
68+
statusManager.dialog.signal_closed.connect(lambda : statusManager.applyDockerMode('dialog.signal_closed'))
69+
statusManager.docker.signal_leaveFloating.connect(lambda : statusManager.applyDialogMode('docker.signal_leaveFloating'))
70+
statusManager.docker.signal_manualOpenDocker.connect(lambda : statusManager.applyDockerMode('docker.signal_manualOpenDocker'))
71+
72+
73+
# Only for debug
74+
testExtension.dynamicAddEntry(DOCKER_ID, dock_widget_factory, centralwidget, dialog, docker, statusManager)
75+
76+
# Dynamically load some object into actions menu
77+
testExtension.dynamicCreateAction(statusManager.applyDockerMode, Krita.instance().activeWindow(), 'PluginDevToolsActionOpenAsDocker', 'Open as Docker', True)
78+
testExtension.dynamicCreateAction(statusManager.applyDialogMode, Krita.instance().activeWindow(), 'PluginDevToolsActionOpenAsDialog', 'Open as Dialog', True)
79+
def toggleOnAndOff():
80+
if statusManager.docker.isVisible():
81+
statusManager.applyHideMode()
82+
else:
83+
statusManager.applyDockerMode()
84+
testExtension.dynamicCreateAction(toggleOnAndOff, Krita.instance().activeWindow(), 'PluginDevToolsToggleOnOff', 'Toggle On or Off')
85+
86+
87+
88+
# Some settings require Krita.instance().activeWindow().qwindow() as parameter
89+
# Wait Krita.instance().activeWindow().qwindow() completely created then set the widgets
90+
appNotifier = Krita.instance().notifier()
91+
appNotifier.setActive(True)
92+
appNotifier.windowCreated.connect(setup)
93+
#appNotifier.applicationClosing.connect(statusManager.saveLastBeforeExit)
94+
95+
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<ActionCollection version="2" name="Scripts">
3+
<Actions category="Scripts">
4+
<text>PluginDevTools</text>
5+
6+
<Action name="PluginDevToolsToggleOnOff">
7+
<icon></icon>
8+
<text>Toggle On or Off</text>
9+
<whatsThis></whatsThis>
10+
<toolTip></toolTip>
11+
<iconText></iconText>
12+
<activationFlags>10000</activationFlags>
13+
<activationConditions>0</activationConditions>
14+
<shortcut>f12</shortcut>
15+
<isCheckable>false</isCheckable>
16+
<statusTip></statusTip>
17+
</Action>
18+
</Actions>
19+
</ActionCollection>
20+
21+
<!-----
22+
user guide:
23+
https://docs.krita.org/en/user_manual/python_scripting/krita_python_plugin_howto.html
24+
save this file in below location:
25+
Windows: C:\Users\%USERNAME%\AppData\Roaming\krita\actions\
26+
Linux: $HOME/.local/share/krita/actions/
27+
----->

0 commit comments

Comments
 (0)