2

EDIT:
The standard QScintilla software doesn't support this feature. But Matic Kukovec made some hacks to get it working anyhow. His solution is explained in his answer below, but also on this webpage: https://qscintilla.com/insert-images/


I would like to insert images in a QScintilla editor. Unfortunately, such a feature won't be added to the official Scintilla project anytime soon. Check out this post on the Scintilla-interest-group:

https://groups.google.com/forum/#!topic/scintilla-interest/Bwr4DY2Pv3Q

So I'll have to implement it myself. I have given it a try. Please copy-past the following code into a python-file and run it. Just make sure you have a qscintilla_logo.png image in the same folder, of around 80 x 80 pixels:

import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.Qsci import *

# -----------------------------------
# | A sample of C-code, pasted into |
# | the QScintilla editor           |
# -----------------------------------
myCodeSample = r"""#include <stdio.h>

/*
 * I want an image
 * right here =>
 */

int main()
{
    char arr[5] = {'h', 'e', 'l', 'l', 'o'};

    int i;

    for(i = 0; i < 5; i++) {
        printf(arr[i]);
    }

    return 0;

}""".replace("\n","\r\n")


# --------------------------------------------------

class CustomMainWindow(QMainWindow):
    def __init__(self):
        super(CustomMainWindow, self).__init__()

        # ---------------------------
        # |      Window setup       |
        # ---------------------------

        # 1. Define the geometry of the main window
        # ------------------------------------------
        self.setGeometry(300, 300, 800, 400)
        self.setWindowTitle("QScintilla Test")

        # 2. Create frame and layout
        # ---------------------------
        self.__frm = QFrame(self)
        self.__frm.setStyleSheet("QWidget { background-color: #ffeaeaea }")
        self.__lyt = QVBoxLayout()
        self.__frm.setLayout(self.__lyt)
        self.setCentralWidget(self.__frm)
        self.__myFont = QFont("Consolas", 14, weight=QFont.Bold)
        self.__myFont.setPointSize(14)

        # 3. Place a button
        # ------------------
        self.__btn = QPushButton("Qsci")
        self.__btn.setFixedWidth(50)
        self.__btn.setFixedHeight(50)
        self.__btn.clicked.connect(self.__btn_action)
        self.__btn.setFont(self.__myFont)
        self.__lyt.addWidget(self.__btn)

        # ---------------------------
        # | QScintilla editor setup |
        # ---------------------------

        # 1. Make instance of QSciScintilla class
        # ----------------------------------------
        self.__editor = QsciScintilla()
        self.__editor.setText(myCodeSample)
        self.__editor.setLexer(None)
        self.__editor.setUtf8(True)  # Set encoding to UTF-8
        self.__editor.setFont(self.__myFont)  # Can be overridden by lexer


        # 2. Create an image
        # -------------------
        self.__myImg = QPixmap("qscintilla_logo.png")
        self.__myImgLbl = QLabel(parent=self.__editor)
        self.__myImgLbl.setStyleSheet("QLabel { background-color : white; }");
        self.__myImgLbl.setPixmap(self.__myImg)
        self.__myImgLbl.move(300,80)


        # 3. Add editor to layout
        # ------------------------
        self.__lyt.addWidget(self.__editor)

        self.show()

    ''''''

    def __btn_action(self):
        print("Hello World!")

    ''''''


''' End Class '''

if __name__ == '__main__':
    app = QApplication(sys.argv)
    QApplication.setStyle(QStyleFactory.create('Fusion'))
    myGUI = CustomMainWindow()

    sys.exit(app.exec_())

''''''

 
When running the Python script, you should get the following result:

enter image description here

I'm actually adding a QLabel on top of the editor, and give it an absolute position:

        self.__myImgLbl = QLabel(parent=self.__editor)
        self.__myImgLbl.move(300,80)

Unfortunately, the image won't move when scrolling down:

enter image description here

I think I know why. Let me explain. The editor is an object of the QsciScintilla class, which is a subclass of QsciScintillaBase, which is a subclass of QAbstractScrollArea:

enter image description here

I believe that the key to success is adding the QLabel to the widget inside the QAbstractScrollArea, not the scroll area itself. I have tried several things to find that widget, but didn't succeed.
If it were a QScrollArea, then the function widget() would be sufficient. But this function is not available on a QAbstractScrollArea.


EDIT 1:
The editor in the python code sample doesn't perform any syntax highlighting, and even doesn't have line numbers. That's because I want to focus only on the actual issue - adding an image. The code sample is based on: https://qscintilla.com/an-editor-in-a-gui/


EDIT 2:
Please, kindly refrain from giving opinions on whether inserting images in an editor is a good or bad idea. Let us just stick to the technical issue at hand.

K.Mulier
  • 8,069
  • 15
  • 79
  • 141
  • The way you're trying to implement this is never going to work. Widget layout and document layout are two entirely separate things. There is nothing you can do to work around this, because Scintilla just does not support adding images to the document layout. If you really want this functionality, you will need to look for a different code-editor widget. – ekhumoro Jun 06 '17 at 13:10
  • Thank you ekhumoro for your comment. What do you mean by "widget layout" and "document layout"? I'm not adding the image to any layout. In fact, I'm giving it an absolute position on top of its `parent` widget. For now, I pass on the QScintilla editor as the parent, because I have nothing else. But I believe that passing on some child widget of the QScintilla editor - the one getting scrolled - is the solution. – K.Mulier Jun 06 '17 at 14:56
  • What do you suppose will happen if the user inserts/deletes 10 lines at the top of the editor? The image is not embedded within the document layout, and so it will not move when the text is edited. If you want embedded images, use a general-purpose rich-text widget, like `QTextEdit`. – ekhumoro Jun 06 '17 at 15:40
  • You are right @ekhumoro. It will be quite a challenge to get it working properly. But switching to QTextEdit is not really a good option either. It doesn't have lexer functionality like QScintilla has (for advanced syntax highlighting). And it has no clickable texts (like the hotspots in QScintilla). – K.Mulier Jun 06 '17 at 17:46
  • There is [QSyntaxHighlighter](https://doc.qt.io/qt-5/qsyntaxhighlighter.html) for lexing. And obviously `QTextEdit` does have clickable texts (i.e. links), since it can render html. – ekhumoro Jun 06 '17 at 18:01
  • Waw, you're right. Thank you @ekhumoro. How would you compare `QScintilla` to the native `QTextEdit` in terms of possibilities? I only know QScintilla. I've not that much experience with QTextEdit. – K.Mulier Jun 06 '17 at 20:24
  • `QScintilla` is a pure code-editor; `QTextEdit` is much more flexible, and can be used as a simple word-processor, an html browser, or a text-editor. Because it's more specialized, there are probably a lot of things that `QScintilla` can do out-of-the-box that `QTextEdit` can't. However, it would surely be much, much simpler to implement something like line-numbering in `QTextEdit`, than image-embedding in `QScintilla`. And I'll bet there's way more `QTextEdit` example code out there than there is for `QScintilla`. – ekhumoro Jun 06 '17 at 20:48
  • In fact, there's already a [Code Editor Example](https://doc.qt.io/qt-5/qtwidgets-widgets-codeeditor-example.html) in the Qt docs that does line numbering, and also a [Syntax Highlighter Example](https://doc.qt.io/qt-5/qtwidgets-richtext-syntaxhighlighter-example.html). – ekhumoro Jun 06 '17 at 20:50
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/146007/discussion-between-k-mulier-and-ekhumoro). – K.Mulier Jun 06 '17 at 20:56

1 Answers1

1

Hey Kristof try this (your example with some modifications):

import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.Qsci import *

# -----------------------------------
# | A sample of C-code, pasted into |
# | the QScintilla editor           |
# -----------------------------------
myCodeSample = r"""#include <stdio.h>

/*
 * I want an image
 * right here =>
 */

int main()
{
    char arr[5] = {'h', 'e', 'l', 'l', 'o'};

    int i;

    for(i = 0; i < 5; i++) {
        printf(arr[i]);
    }

    return 0;

}""".replace("\n","\r\n")


# --------------------------------------------------
class MyScintilla(QsciScintilla):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.image = QImage("qscintilla_logo.png")

    def paintEvent(self, e):
        super().paintEvent(e)
        # Get paint rectangle size
        current_parent_size = self.size()
        image_line = 4
        image_column = 17
        # Find the paint offset
        line_height = 20
        font_width = 10
        first_visible_line = self.SendScintilla(self.SCI_GETFIRSTVISIBLELINE)
        paint_offset_x = image_column * font_width
        paint_offset_y = (image_line - first_visible_line) * line_height
        # Paint the image
        painter = QPainter()
        painter.begin(self.viewport())
        painter.drawImage(QPoint(paint_offset_x,paint_offset_y), self.image)
        painter.end()


class CustomMainWindow(QMainWindow):
    def __init__(self):
        super(CustomMainWindow, self).__init__()

        # ---------------------------
        # |      Window setup       |
        # ---------------------------

        # 1. Define the geometry of the main window
        # ------------------------------------------
        self.setGeometry(300, 300, 800, 400)
        self.setWindowTitle("QScintilla Test")

        # 2. Create frame and layout
        # ---------------------------
        self.__frm = QFrame(self)
        self.__frm.setStyleSheet("QWidget { background-color: #ffeaeaea }")
        self.__lyt = QVBoxLayout()
        self.__frm.setLayout(self.__lyt)
        self.setCentralWidget(self.__frm)
        self.__myFont = QFont("Consolas", 14, weight=QFont.Bold)
        self.__myFont.setPointSize(14)

        # 3. Place a button
        # ------------------
        self.__btn = QPushButton("Qsci")
        self.__btn.setFixedWidth(50)
        self.__btn.setFixedHeight(50)
        self.__btn.clicked.connect(self.__btn_action)
        self.__btn.setFont(self.__myFont)
        self.__lyt.addWidget(self.__btn)

        # ---------------------------
        # | QScintilla editor setup |
        # ---------------------------

        # 1. Make instance of QSciScintilla class
        # ----------------------------------------
        self.__editor = MyScintilla()
        self.__editor.setText(myCodeSample)
        self.__editor.setLexer(None)
        self.__editor.setUtf8(True)  # Set encoding to UTF-8
        self.__editor.setFont(self.__myFont)  # Can be overridden by lexer


        # 3. Add editor to layout
        # ------------------------
        self.__lyt.addWidget(self.__editor)

        self.show()

    ''''''

    def __btn_action(self):
        print("Hello World!")

    ''''''


''' End Class '''

if __name__ == '__main__':
    app = QApplication(sys.argv)
    QApplication.setStyle(QStyleFactory.create('Fusion'))
    myGUI = CustomMainWindow()

    sys.exit(app.exec_())

''''''

It is just subclassing the QsciScintilla class and overloading it's paintEvent method. The trick is to get the coordinates of where the image is in relation to the view of the document. For the example I justed guessed the line height and font width, it can probably be obtained from the QScintilla. The other thing is I just hard coded the position of the image with a line and column offset, it would probably be good to create a syntax that the editor could parse and insert the image automatically.

Regards

Matic Kukovec
  • 354
  • 2
  • 9