How can I listen to foregroundColorChanged?

[Edit: later in the thread, the question became "how can I listen for the event "user painted something “”. I’ve posted a working plugin that sets the foreground color to the previously used one.]

original message:

Hi, how can I listen to the foregound color changed signal in a python plugin?

I’m trying to make an action that selects the last used color (equivalent to clicking the topmost color in the color history)

I’m completely new to krita plugins. So far I’ve been able to set the foreground color to a fixed value, but I’m clueless how to be notified of fg color changes, so I can set the previous color instead of a fixed one. By browsing the cpp source, it seems there is a signal called foregroundColorChanged in KoDualColorButton, but I don’t know how to proceed. Thanks for help

To be clear? You are making a python plugin right?

from krita import *

def colorChanged():
    print( Krita.instance().activeWindow().activeView().foregroundColor().toXML() )

qwin = Krita.instance().activeWindow().qwindow()
wobj = qwin.findChild(QTableView,'paletteBox')
wobj.selectionModel().currentChanged.connect(colorChanged)

PS I suggest Krita Python Plugin Developer Tools, it is a lot easier than going through cpp file source code.

1 Like

Thank you! so apparently I need the PaletteBox class, currentChanged signal!

I am trying it but I might be missing something about how the Scripter works: should something show up in the output area, when I change the foreground color via the Advanced Color Selection? If so, it’s not working:

Scripter print is a bit weird when printing is done using delayed callbacks. (workaround is to print to log file or own text widget)

from krita import (
        Krita,)

from PyQt5.QtCore import (
        Qt,)

from PyQt5.QtWidgets import (
        QTextEdit,
        QTableView)


class MyLogger(QTextEdit):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.setWindowFlag(Qt.WindowStaysOnTopHint)
        self.listen_color_changes()

    def listen_color_changes(self):
        app = Krita.instance()
        win = app.activeWindow()
        qwin = win.qwindow()
        paletteBox = qwin.findChild(QTableView, 'paletteBox')
        s_model = paletteBox.selectionModel()
        s_model.currentChanged.connect(self._on_colorChanged)

    def _on_colorChanged(self, current, previous):
        app = Krita.instance()
        win = app.activeWindow()
        view = win.activeView()
        xml_color = view.foregroundColor().toXML()
        self.append(xml_color)


my_logger = MyLogger()
my_logger.resize(800, 80)
my_logger.show()

/AkiR

1 Like

That doesn’t seem to be the problem though. At least on my Krita 5, the handler (I think it’s called slot) doesn’t seem to be called. Nothing is showing in the custom debug window, as I change color. Does the code work on your machine? (Do I need Krita 4? Is it enough to press the play button?) Thanks again :slight_smile:

strange, I’m runnin code also in Win Krita 5.0.0, and it registers foreground color changes just fine…
are you changing foreground color from Palette docker?

/AkiR

1 Like

Sorry, I’m actually using the “advanced color selector” and the “color history” to change color:

immagine

If I click a color from the palette , I now see that the handler is called! :slight_smile:

To be precise, if I use the Advanded Color Selector, the handler is only called when the change causes the palette to change its highlighted color. Not always. And at any rate, the color returned is the one from the palette, which is not precisely the one I chose from the selector (it’s the closest one in the palette).

Can the method be adapted to listen to the “Advanced Color Selector” and color history instead?

(PS: Right now I’m trying a polling with a timer and I might be able to make it )

Use krita.com from command prompt instead of the exe, it should print to the command line.

You can also monitor all events and signals for a widgets via the Krita Python Developer Tools via the event/signal viewer, it will show you when they run as well as the order the signals are ran in.

The signal as-is checks for palette changes, but the actual color found is not from the palette but the foregroundcolor.

1 Like
from krita import (
        Krita,)

from PyQt5.QtCore import (
        Qt,
        QObject,
        pyqtSlot)

from PyQt5.QtWidgets import (
        QTextEdit,
        QTableView)


class MyLogger(QTextEdit):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.setWindowFlag(Qt.WindowStaysOnTopHint)
        self.listen_color_changes()

    def listen_color_changes(self):
        app = Krita.instance()
        colorSelectorNg = next((d for d  in app.dockers() if d.objectName() == 'ColorSelectorNg'), None)
        for child in colorSelectorNg.findChildren(QObject):
            meta = child.metaObject()
            if meta.className() in {
                        'KisColorSelectorRing', 'KisColorSelectorTriangle',
                        'KisColorSelectorSimple', 'KisColorSelectorWheel'}:
                sig = getattr(child, 'update')
                sig.connect(self._on_color_selector_update)

    @pyqtSlot()
    def _on_color_selector_update(self):
        app = Krita.instance()
        win = app.activeWindow()
        view = win.activeView()
        xml_color = view.foregroundColor().toXML()
        self.append(xml_color)


my_logger = MyLogger()
my_logger.resize(800, 80)
my_logger.show()

new version, now existing {‘KisColorSelectorRing’, ‘KisColorSelectorTriangle’, ‘KisColorSelectorSimple’, ‘KisColorSelectorWheel’} widgets update signals are connected to MyLogger._on_color_selector_changes() slot. (if color selector is changed to new type for first time, connections fail)

/AkiR

1 Like

Sorry, I’m afraid I made a huge mistake. :expressionless: I don’t need to be notified when the foreground color changes, but when the user actually draws a pixel. If the user sets a new color but does not actually use it to paint, I shouldn’t record it. Sorry guys. Is there a way to listen to the event “the user has painted a pixel”? (with the pen tool is enough)

You would have to install an event filter on the QOpenGLWidget and look for event 207 InputMethodQuery, then keep a record of the old color and new color

But keep note that new QOpenGLWidget is made every time you open a new view.

Edit: Going with the history docker way as Akir posted is probably easier and better as you only need it once and I would avoid eventfilters when possible.

And now great history was made! (and recorded)

from time import time

from krita import (
        Krita,)

from PyQt5.QtCore import (
        Qt,
        QObject,
        pyqtSlot,
        QModelIndex)

from PyQt5.QtWidgets import (
        QTextEdit,
        QListView)


class MyLogger(QTextEdit):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.setWindowFlag(Qt.WindowStaysOnTopHint)
        self.listen_changes()

    def listen_changes(self):
        app = Krita.instance()
        history_docker = next((d for d in app.dockers() if d.objectName() == 'History'), None)
        kis_undo_view = next((v for v in history_docker.findChildren(QListView) if v.metaObject().className() == 'KisUndoView'), None)
        s_model = kis_undo_view.selectionModel()
        s_model.currentChanged.connect(self._on_history_was_made)

    @pyqtSlot(QModelIndex, QModelIndex)    
    def _on_history_was_made(self, current, previous):
        # double signal bug?
        self.append(f'User did make history! {time()}')


my_logger = MyLogger()
my_logger.resize(800, 80)
my_logger.show()

/AkiR

1 Like

It works man! Thank you very much!! :pray: :pray: :pray:

Here is the full code if anyone’s interested: (probably not good code, but it works)

This plugin provides an action that is equivalent to clicking the topmost color from the color history. I’ve bound it to the X key.

from krita import *
# from PyQt5 import QtWidgets, QtCore, QtGui, uic
import os.path
import time
import datetime
import xml



def listEqual(l1,l2):
   if(len(l1)==len(l2) and len(l1)==sum([1 for i,j in zip(l1,l2) if i==j])):
      return True
   else:
      return False
      
      
class MyExtension(Extension):

    def __init__(self, parent):
        # This is initialising the parent, always important when subclassing.
        super().__init__(parent)
        
        
        


        
    def setup(self):
        
        
        
        
        self.currentColor = [0,0,0,0]
        self.previousColor = [0,0,0,0]
        self.inited = False
        
        
        pass

        
        
    
    def swap(self):
        try:
        
        
            if not self.inited:
                print ("swap: initializing...")
                app = Krita.instance()
                history_docker = next((d for d in app.dockers() if d.objectName() == 'History'), None)
                kis_undo_view = next((v for v in history_docker.findChildren(QListView) if v.metaObject().className() == 'KisUndoView'), None)
                s_model = kis_undo_view.selectionModel()
                s_model.currentChanged.connect(self._on_history_was_made)
                
                self.inited = True;
                print ("swap: initialized")
            else:
                
                acView = Krita.instance().activeWindow().activeView()

                col = acView.foregroundColor()
                comp = col.components()
                
                
                col.setComponents(self.previousColor)
                acView.setForeGroundColor(col)
                
                self.previousColor = self.currentColor
                    
        except Exception as e:
                print("errore trovato in swap:")
                print(e)
                
                


    
    def _on_history_was_made(self):
        # print ("user painted")
        try:
            
            acView = Krita.instance().activeWindow().activeView()
            if acView is not None: 
                col = acView.foregroundColor()
                if col is not None:   
                    comp = col.components()
                    if listEqual( comp, self.currentColor ):   # user did not change color
                         pass
                    else:  # user changed color
                        #print("color changed")
                        self.previousColor = list.copy(self.currentColor) 
                        self.currentColor = list.copy(comp)
                

                
        except:
                print("found error")
                #self.timer_hm.stop()
                raise

    def createActions(self, window):
    


        action = window.createAction("LastColor", "LastColor")
        action.triggered.connect(self.swap)

# And add the extension to Krita's list of extensions:
Krita.instance().addExtension(MyExtension(Krita.instance()))


2 Likes