PyQt4: Auto completion in Qscintilla and horizontal scrolling - python

I want to show all attributes and tags in auto completion list of a html file if auto completion threshold is set to 1. I have tried this code to use APIs i set this code after the file is loaded in new mdi child(sub window) but it is not working:
lexer=Qsci.QsciLexerHTML()
api = Qsci.QsciAPIs(lexer)
## Add autocompletion strings
api.add("aLongString")
api.add("aLongerString")
api.add("aDifferentString")
api.add("sOmethingElse")
## Compile the api for use in the lexer
api.prepare()
self.activeMdiChild().setAutoCompletionSource(Qsci.QsciScintilla.AcsAPIs)
self.activeMdiChild().setLexer(lexer)
and my horizontal scroll bar is visible all the time i want to set it as scrollbarasneeded. please tell how to do these two tasks.

Other than failing to set the auto-completion threshold, there doesn't seem to be anything wrong with your example code. Here's a minimal working example:
from PyQt4 import QtGui, Qsci
class Window(Qsci.QsciScintilla):
def __init__(self):
Qsci.QsciScintilla.__init__(self)
lexer = Qsci.QsciLexerHTML(self)
api = Qsci.QsciAPIs(lexer)
api.add('aLongString')
api.add('aLongerString')
api.add('aDifferentString')
api.add('sOmethingElse')
api.prepare()
self.setAutoCompletionThreshold(1)
self.setAutoCompletionSource(Qsci.QsciScintilla.AcsAPIs)
self.setLexer(lexer)
if __name__ == "__main__":
import sys
app = QtGui.QApplication(sys.argv)
window = Window()
window.show()
app.exec_()
The scrollbar-as-needed feature cannot really be solved, unless you are willing to reimplement everything yourself (which would not be easy). The underlying Scintilla control doesn't directly support automatic horizontal scrollbar hiding, because it involves a potentially very expensive calculation (i.e. determining the longest line). Most people who use Scintilla/Qscintilla just learn to put up with the ever-present horizontal scrollbar.

Related

How PySide6 paintEvent function for a PushButton works?

A long time ago, I wanted to make a logo appear on top of the text in a QPushButton stacked on top of each other, but I couldn't find anyway
I read some stylesheets (couldn't find a single doc to read it all about all styles I can apply to a button)
tried the setLayoutDirection (RightToLeft and LeftToRight were there, but no UpToDown direction)
In my (I wish) last attempt I tried to inherit a QAbstractButton (I didn't find QAbstractPushButton, so I guess QAbstractButton is the answer) and change its paintEvent/paintEngine to draw an image or maybe add a vbox inside it as a layout to draw to components, but I can't find anything in python (specially PySide) which has an example in any possible way close to that. The best thing I found was the analogue clock example which was not very helpful because it was trying to work a QWidget and not a QAbstractButton and I want to keep the feel of a Native looking button.
I like my final product to be something like this.
source of the implemention of that
Python Enaml toolkit supported this feature out of the box (in one of its widgets), and I know it is QT based, so I really wish to know how it is possible?
p.s.: Also, is there a market for qt widgets? e.g.: a plugin system. Because rewriting an android like switch doesn't seem like the correct thing that I should do! even a good tutorial or doc would be appreicated (excluding official doc)
It is easier than you think, you can use QToolButton() like this:
import sys
from PySide6.QtCore import Qt, QSize
from PySide6.QtWidgets import QApplication, QVBoxLayout,QStyle, QWidget,
QToolButton
class Window(QWidget):
def __init__(self, parent=None):
super(Window, self).__init__(parent)
button = QToolButton()
# here you choose the position of the icon and its text
button.setToolButtonStyle(
Qt.ToolButtonStyle.ToolButtonTextUnderIcon)
# here I just use built-in icon by PySide6 for this example
name = 'SP_DialogSaveButton'
pixmapi = getattr(QStyle, name)
icon = self.style().standardIcon(pixmapi)
# here we set text and icon of size 32x32 to the button
button.setIcon(icon)
button.setText("Sample text")
button.setIconSize(QSize(32, 32))
# finally we add our button to the layout
lay = QVBoxLayout(self)
lay.addWidget(button, alignment=Qt.AlignCenter)
if __name__ == "__main__":
app = QApplication(sys.argv)
win = Window()
win.show()
sys.exit(app.exec())

Maya Python: Button always at the center of the window

I'm starting experimenting with Maya python, and I'm trying to do some UI.
I came across to a really strange problem, I can't get a button to stay in the center of the windows.
I've tried different things but nothing seems to work, here is the code:
import maya.cmds as cmds
cmds.window( width=200 )
WS = mc.workspaceControl("dockName", retain = False, floating = True,mw=80)
submit_widget = cmds.rowLayout(numberOfColumns=1, p=WS)
cmds.button( label='Submit Job',width=130,align='center', p=submit_widget)
cmds.showWindow()
this is a simple version but still, I can't get it to work.
can someone help me?
I honestly don't know the answer as anytime I have to dig into Maya's native UI stuff it makes me question my own life.
So I know it's not exactly what you're asking for, but I'll opt with this: Use PySide instead. At first glance it might make you go "woah, that's way too hard", but it's also a million times better (and actually easier). It's much more powerful, flexible, has great documentation, and also used outside of Maya (so actually useful to learn). Maya's own interface uses the same framework, so you can even edit it with PySide once you're more comfortable with it.
Here's a bare-bones example to create a centered button in a window:
# Import PySide libraries.
from PySide2 import QtCore
from PySide2 import QtWidgets
class MyWindow(QtWidgets.QWidget): # Create a class for our window, which inherits from `QWidget`
def __init__(self, parent=None): # The class's constructor.
super(MyWindow, self).__init__(parent) # Initialize its `QWidget` constructor method.
self.my_button = QtWidgets.QPushButton("My button!") # Create a button!
self.my_layout = QtWidgets.QVBoxLayout() # Create a vertical layout!
self.my_layout.setAlignment(QtCore.Qt.AlignCenter) # Center the horizontal alignment.
self.my_layout.addWidget(self.my_button) # Add the button to the layout.
self.setLayout(self.my_layout) # Make the window use this layout.
self.resize(300, 300) # Resize the window so it's not tiny.
my_window_instance = MyWindow() # Create an instance of our window class.
my_window_instance.show() # Show it!
Not too bad, right?

pyqt5 textedit delete lines as they move past specified line

I'd like to setup the PYQT Qtextedit widget and use it to monitor another applications activity log(like tail -f on Linux). Long term I worry about it running for too long and using a lot of ram with the text that builds up. Is it possible to set a limit so that text moving past line x gets deleted? From what I've found it seems to require custom work and I'd like to find a limiter setting if one exists.
QPlainTextEdit is an advanced viewer/editor supporting plain text. It is optimized to handle large documents and to respond quickly to user input.
To limit the number of visible lines you must use setMaximumBlockCount, in the following example I show the use:
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
counter = 0
def addText():
global counter
w.appendHtml("<font size=\"3\" color=\"red\">{}</font>".format(counter))
counter += 1
if __name__ == "__main__":
app = QApplication(sys.argv)
w = QPlainTextEdit()
timer = QTimer()
timer.timeout.connect(addText)
timer.start(1000)
w.setMaximumBlockCount(4)
w.show()
sys.exit(app.exec_())
If you want to use fonts you can do it easily using HTML.

QScintilla based text editor in PyQt5 with clickable functions and variables

I am trying to make a simple texteditor with basic syntax highlighting, code completion and clickable functions & variables in PyQt5. My best hope to achieve this is using the QScintilla port
for PyQt5.
I have found the following QScintilla-based texteditor example on the Eli Bendersky website (http://eli.thegreenplace.net/2011/04/01/sample-using-qscintilla-with-pyqt, Victor S. has adapted it to PyQt5). I think this example is a good starting point:
#-------------------------------------------------------------------------
# qsci_simple_pythoneditor.pyw
#
# QScintilla sample with PyQt
#
# Eli Bendersky (eliben#gmail.com)
# This code is in the public domain
#-------------------------------------------------------------------------
import sys
import sip
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.Qsci import QsciScintilla, QsciLexerPython
class SimplePythonEditor(QsciScintilla):
ARROW_MARKER_NUM = 8
def __init__(self, parent=None):
super(SimplePythonEditor, self).__init__(parent)
# Set the default font
font = QFont()
font.setFamily('Courier')
font.setFixedPitch(True)
font.setPointSize(10)
self.setFont(font)
self.setMarginsFont(font)
# Margin 0 is used for line numbers
fontmetrics = QFontMetrics(font)
self.setMarginsFont(font)
self.setMarginWidth(0, fontmetrics.width("00000") + 6)
self.setMarginLineNumbers(0, True)
self.setMarginsBackgroundColor(QColor("#cccccc"))
# Clickable margin 1 for showing markers
self.setMarginSensitivity(1, True)
# self.connect(self,
# SIGNAL('marginClicked(int, int, Qt::KeyboardModifiers)'),
# self.on_margin_clicked)
self.markerDefine(QsciScintilla.RightArrow,
self.ARROW_MARKER_NUM)
self.setMarkerBackgroundColor(QColor("#ee1111"),
self.ARROW_MARKER_NUM)
# Brace matching: enable for a brace immediately before or after
# the current position
#
self.setBraceMatching(QsciScintilla.SloppyBraceMatch)
# Current line visible with special background color
self.setCaretLineVisible(True)
self.setCaretLineBackgroundColor(QColor("#ffe4e4"))
# Set Python lexer
# Set style for Python comments (style number 1) to a fixed-width
# courier.
#
lexer = QsciLexerPython()
lexer.setDefaultFont(font)
self.setLexer(lexer)
text = bytearray(str.encode("Arial"))
# 32, "Courier New"
self.SendScintilla(QsciScintilla.SCI_STYLESETFONT, 1, text)
# Don't want to see the horizontal scrollbar at all
# Use raw message to Scintilla here (all messages are documented
# here: http://www.scintilla.org/ScintillaDoc.html)
self.SendScintilla(QsciScintilla.SCI_SETHSCROLLBAR, 0)
# not too small
self.setMinimumSize(600, 450)
def on_margin_clicked(self, nmargin, nline, modifiers):
# Toggle marker for the line the margin was clicked on
if self.markersAtLine(nline) != 0:
self.markerDelete(nline, self.ARROW_MARKER_NUM)
else:
self.markerAdd(nline, self.ARROW_MARKER_NUM)
if __name__ == "__main__":
app = QApplication(sys.argv)
editor = SimplePythonEditor()
editor.show()
editor.setText(open(sys.argv[0]).read())
app.exec_()
Just copy-paste this code into an empty .py file, and run it. You should get the following simple texteditor appearing on your display:
Notice how perfect the syntax highlighting is! QScintilla certainly did some parsing on the background to achieve that.
Is it possible to make clickable functions & variables for this texteditor? Every self-respecting IDE has it. You click on a function, and the IDE jumps to the function definition. The same for variables. I would like to know:
Does QScintilla support clickable functions & variables?
If not, is it possible to import another python module that implements this feature in the QScintilla texteditor?
EDIT :
λuser noted the following:
Clickable function names require full parsing with a much deeper knowledge of a programming language [..]This is way beyond the scope of Scintilla/QScintilla. Scintilla provides a way to react when the mouse clicks somewhere on the text, but the logic of "where is the definition of a function" is not in Scintilla and probably never will be.However, some projects are dedicated to this task, like ctags. You could simply write a wrapper around this kind of tool.
I guess that writing such wrapper for ctags is now on my TODO list. The very first step is to get a reaction (Qt signal) when the user clicks on a function or variable. And perhaps the function/variable should turn a bit blueish when you hover with the mouse over it, to notify the user that it is clickable. I already tried to achieve this, but am held back by the shortage of QScintilla documentation.
So let us trim down the question to: How do you make a function or variable in the QScintilla texteditor clickable (with clickable defined as 'something happens')
EDIT :
I just returned to this question now - several months later. I have been cooperating with my friend Matic Kukovec to design a website about QScintilla. It is a beginner-friendly tutorial on how to use it:
https://qscintilla.com/
I hope this initiative fills the gap of lacking documentation.
Syntax highlighting is just a matter of running a lexer on the source file to find tokens, then attribute styles to it. A lexer has a very basic understanding of a programming language, it only understands what is a number literal, a keyword, an operator, a comment, a few others and that's all. This is a somewhat simple job that can be performed with just regular expressions.
On the other hand, clickable function names requires requires full parsing with a much deeper knowledge of a programming language, e.g. is this a declaration of a variable or a use, etc. Furthermore, this may require parsing other source files not opened by current editor.
This is way beyond the scope of Scintilla/QScintilla. Scintilla provides a way to react when the mouse clicks somewhere on the text, but the logic of "where is the definition of a function" is not in Scintilla and probably never will be.
However, some projects are dedicated to this task, like ctags. You could simply write a wrapper around this kind of tool.
A way to use Pyqt5 with option with clickable functions and variables.
Your script that have the clickable part Quoted out, would look like this in PyQt5 with a custom signal.
PyQt4 SIGNAL
self.connect(self,SIGNAL('marginClicked(int, int, Qt::KeyboardModifiers)'),
self.on_margin_clicked)
PyQt5 SIGNAL
self.marginClicked.connect(self.on_margin_clicked)
PyQt5
import sys
import sip
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.Qsci import QsciScintilla, QsciLexerPython
class SimplePythonEditor(QsciScintilla):
ARROW_MARKER_NUM = 8
def __init__(self, parent=None):
super(SimplePythonEditor, self).__init__(parent)
# Set the default font
font = QFont()
font.setFamily('Courier')
font.setFixedPitch(True)
font.setPointSize(10)
self.setFont(font)
self.setMarginsFont(font)
# Margin 0 is used for line numbers
fontmetrics = QFontMetrics(font)
self.setMarginsFont(font)
self.setMarginWidth(0, fontmetrics.width("00000") + 6)
self.setMarginLineNumbers(0, True)
self.setMarginsBackgroundColor(QColor("#cccccc"))
# Clickable margin 1 for showing markers
self.setMarginSensitivity(1, True)
self.marginClicked.connect(self.on_margin_clicked)
self.markerDefine(QsciScintilla.RightArrow,
self.ARROW_MARKER_NUM)
self.setMarkerBackgroundColor(QColor("#ee1111"),
self.ARROW_MARKER_NUM)
# Brace matching: enable for a brace immediately before or after
# the current position
#
self.setBraceMatching(QsciScintilla.SloppyBraceMatch)
# Current line visible with special background color
self.setCaretLineVisible(True)
self.setCaretLineBackgroundColor(QColor("#ffe4e4"))
# Set Python lexer
# Set style for Python comments (style number 1) to a fixed-width
# courier.
#
lexer = QsciLexerPython()
lexer.setDefaultFont(font)
self.setLexer(lexer)
text = bytearray(str.encode("Arial"))
# 32, "Courier New"
self.SendScintilla(QsciScintilla.SCI_STYLESETFONT, 1, text)
# Don't want to see the horizontal scrollbar at all
# Use raw message to Scintilla here (all messages are documented
# here: http://www.scintilla.org/ScintillaDoc.html)
self.SendScintilla(QsciScintilla.SCI_SETHSCROLLBAR, 0)
# not too small
self.setMinimumSize(600, 450)
def on_margin_clicked(self, nmargin, nline, modifiers):
# Toggle marker for the line the margin was clicked on
if self.markersAtLine(nline) != 0:
self.markerDelete(nline, self.ARROW_MARKER_NUM)
else:
self.markerAdd(nline, self.ARROW_MARKER_NUM)
if __name__ == "__main__":
app = QApplication(sys.argv)
editor = SimplePythonEditor()
editor.show()
editor.setText(open(sys.argv[0]).read())
app.exec_()
I got a helpful answer from Matic Kukovec through mail, that I would like to share here. Matic Kukovec made an incredible IDE based on QScintilla: https://github.com/matkuki/ExCo. Maybe it will inspire more people to dig deeper into QScintilla (and clickable variables and functions).
Hotspots make text clickable. You have to style it manualy using the QScintilla.SendScintilla function.
Example function I used in my editor Ex.Co. ( https://github.com/matkuki/ExCo ):
def style_hotspot(self, index_from, length, color=0xff0000):
"""Style the text from/to with a hotspot"""
send_scintilla =
#Use the scintilla low level messaging system to set the hotspot
self.SendScintilla(PyQt4.Qsci.QsciScintillaBase.SCI_STYLESETHOTSPOT, 2, True)
self.SendScintilla(PyQt4.Qsci.QsciScintillaBase.SCI_SETHOTSPOTACTIVEFORE, True, color)
self.SendScintilla(PyQt4.Qsci.QsciScintillaBase.SCI_SETHOTSPOTACTIVEUNDERLINE, True)
self.SendScintilla(PyQt4.Qsci.QsciScintillaBase.SCI_STARTSTYLING, index_from, 2)
self.SendScintilla(PyQt4.Qsci.QsciScintillaBase.SCI_SETSTYLING, length, 2)
This makes text in the QScintilla editor clickable when you hover the mouse over it.
The number 2 in the above functions is the hotspot style number.
To catch the event that fires when you click the hotspot, connect to these signals:
QScintilla.SCN_HOTSPOTCLICK
QScintilla.SCN_HOTSPOTDOUBLECLICK
QScintilla.SCN_HOTSPOTRELEASECLICK
For more details look at Scintilla hotspot documentation:
http://www.scintilla.org/ScintillaDoc.html#SCI_STYLESETHOTSPOT
and QScintilla hotspot events:
http://pyqt.sourceforge.net/Docs/QScintilla2/classQsciScintillaBase.html#a5eff383e6fa96cbbaba6a2558b076c0b
First of all, a big thank you to Mr. Kukovec! I have a few questions regarding your answer:
(1) There are a couple of things I don't understand in your example function.
def style_hotspot(self, index_from, length, color=0xff0000):
"""Style the text from/to with a hotspot"""
send_scintilla = # you undefine send_scintilla?
#Use the scintilla low level messaging system to set the hotspot
self.SendScintilla(..) # What object does 'self' refer to in this
self.SendScintilla(..) # context?
self.SendScintilla(..)
(2) You say "To catch the event that fires when you click the hotspot, connect to these signals:"
QScintilla.SCN_HOTSPOTCLICK
QScintilla.SCN_HOTSPOTDOUBLECLICK
QScintilla.SCN_HOTSPOTRELEASECLICK
How do you actually connect to those signals? Could you give one example? I'm used to the PyQt signal-slot mechanism, but I never used it on QScintilla. It would be a big help to see an example :-)
(3) Maybe I missed something, but I don't see where you define in QScintilla that functions and variables (and not other things) are clickable in the source code?
Thank you so much for your kind help :-)
Have a look at the following documentation:
https://qscintilla.com/#clickable_text
There are two ways to make things clickable in Qscintilla - you can use hotspots or indicators. hotspots require you to override the default behavior of underlying lexer, but indicators are more convenient for your use case, I think.
I suggest you have a look at indicators which can help you to make text clickable and you can define event handlers that get executed when it gets clicked.
https://qscintilla.com/#clickable_text/indicators

PyQt4 SIGNAL/SLOT problem when using sub-directories

Thanks in advance for taking the time to read this. Apologies that it is somewhat verbose. But hopefully it fully explains the problem. Stripped code demonstrating the issue is included.
I'm having an issue with PyQt4 SIGNAL/SLOTS. While I can make everything work fine if I am writing in a single file, I can't make things work if I some of the functions I wish to use are moved to sub-directories/classes.
I've looked through the Python Bindings document I can see how this works when using a single file. But what I am trying to do is this:
main.py file in root dir which contains the MainWindow __init__ code.
This file imports a number of widgets. Each widget is stored in its own sub-directory. All sub-directories contain an __init__.py file. These sub-directories are inside of a directory called 'bin', which is itself in the root dir
Some of these widgets need to have SIGNAL/SLOT links between them This is where I fall down.
So the file structure is:
- main.py
- bin/textEditor/__init__.py
- bin/textEditor/plugin.py
- bin/logWindow/__init__.py
- bin/logWindow/plugin.py
The following code shows the problem. This code creates a very basic main window that contains a central QTextEdit() widget and a dockable QTextEdit() widget. All that happens is that when the text in the central widget is changed, the same text is shown in the dockable widget. The example works. But it does so by connecting the signal textChanged() in the bin/textEditor/plugin.py file that creates the central QTextEdit() with a function in main.py. I would like it to do exactly the same thing but connexted to the updateUi function in bin/textEditor/plugin.py
If anyone could shed some light on this, I would be hugely grateful. I'm sure it is simple. But direction to any tutorials that cover this or statements that I am doing it all very wrong are equally appreciated!. Thanks again for your time:
### main.py
import os
import sys
# Import PyQT modules
from PyQt4.QtCore import *
from PyQt4.QtGui import *
# Start the main class
class MainWindow(QMainWindow):
# Initialise
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
# Name and size the main window
self.setWindowTitle("EDITOR/LOG")
self.resize(800, 600)
import bin.logWindow.plugin as logWindow
logWindow.create(self)
import bin.textEditor.plugin as textEditor
textEditor.create(self)
def updateUi(self):
# I can connect to this function from within bin/textEditor/plugin.py (see
# below) but I want to connect to the function located in
# bin/textEditor/plugin.py instead
text = self.editor.toPlainText()
self.logWidget.setText(text)
# Run the app
def main():
app = QApplication(sys.argv)
form = MainWindow()
form.show()
app.exec_()
# Call main
main()
The code inside of the two plugin files is:
### bin/textEditor/plugin.py
# Import PyQT modules
from PyQt4.QtCore import *
from PyQt4.QtGui import *
def create(self):
# Add a dockable widget
self.logDockWidget = QDockWidget("Log", self)
self.logDockWidget.setObjectName("LogDockWidget")
self.logDockWidget.setAllowedAreas(Qt.LeftDockWidgetArea|
Qt.RightDockWidgetArea)
self.logWidget = QTextEdit()
self.logDockWidget.setWidget(self.logWidget)
self.addDockWidget(Qt.LeftDockWidgetArea, self.logDockWidget)
And
### bin/logWindow/plugin.py
Import PyQT modules
from PyQt4.QtCore import *
from PyQt4.QtGui import *
def create(self):
# Create a text editing box
self.editor = QTextEdit()
# Add to main window
self.setCentralWidget(self.editor)
# connect text change to update log window. This is presumably what I need to
# change so that it connects to the function below instead of the on in main.py
self.connect(self.editor, SIGNAL("textChanged()"), self.updateUi)
def updateUi(self):
text = self.editor.toPlainText()
self.logWidget.setText(text)
For starters, is there a reason you're using a very old version of the PyQt release document? The new one is: here
There are a few things you are doing that are a bit unusual. Generally import statements in python are placed at the top of the file (to more easily see dependencies), but I assume you're doing this to support a more generalized import system for plugins in the future.
It seems like the basic problem is you're trying to connect a signal source to a slot in another object, without storing that other object in a particular place. To do this you probably need to either make the connection in main, make a neutral "updateUi" slot that emits it's own special signal that all the plugins are waiting for, or just keep a reference to those subobjects in main and be careful with the initialization order.

Categories