QtSingleApplication for PySide or PyQt - python

Is there a Python version of the C++ class QtSingleApplication from Qt Solutions?
QtSingleApplication is used to make sure that there can never be more than one instance of an application running at the same time.

Here is my own implementation.
It has been tested with Python 2.7 and PySide 1.1.
It has essentially the same interface as the C++ version of QtSingleApplication. The main difference is that you must supply an application unique id to the constructor. (The C++ version by default uses the path to the executable as a unique id; that would not work here because the executable will most likely be python.exe.)
from PySide.QtCore import *
from PySide.QtGui import *
from PySide.QtNetwork import *
class QtSingleApplication(QApplication):
messageReceived = Signal(unicode)
def __init__(self, id, *argv):
super(QtSingleApplication, self).__init__(*argv)
self._id = id
self._activationWindow = None
self._activateOnMessage = False
# Is there another instance running?
self._outSocket = QLocalSocket()
self._outSocket.connectToServer(self._id)
self._isRunning = self._outSocket.waitForConnected()
if self._isRunning:
# Yes, there is.
self._outStream = QTextStream(self._outSocket)
self._outStream.setCodec('UTF-8')
else:
# No, there isn't.
self._outSocket = None
self._outStream = None
self._inSocket = None
self._inStream = None
self._server = QLocalServer()
self._server.listen(self._id)
self._server.newConnection.connect(self._onNewConnection)
def isRunning(self):
return self._isRunning
def id(self):
return self._id
def activationWindow(self):
return self._activationWindow
def setActivationWindow(self, activationWindow, activateOnMessage = True):
self._activationWindow = activationWindow
self._activateOnMessage = activateOnMessage
def activateWindow(self):
if not self._activationWindow:
return
self._activationWindow.setWindowState(
self._activationWindow.windowState() & ~Qt.WindowMinimized)
self._activationWindow.raise_()
self._activationWindow.activateWindow()
def sendMessage(self, msg):
if not self._outStream:
return False
self._outStream << msg << '\n'
self._outStream.flush()
return self._outSocket.waitForBytesWritten()
def _onNewConnection(self):
if self._inSocket:
self._inSocket.readyRead.disconnect(self._onReadyRead)
self._inSocket = self._server.nextPendingConnection()
if not self._inSocket:
return
self._inStream = QTextStream(self._inSocket)
self._inStream.setCodec('UTF-8')
self._inSocket.readyRead.connect(self._onReadyRead)
if self._activateOnMessage:
self.activateWindow()
def _onReadyRead(self):
while True:
msg = self._inStream.readLine()
if not msg: break
self.messageReceived.emit(msg)
Here is a simple test program:
import sys
from PySide.QtGui import *
from QtSingleApplication import QtSingleApplication
appGuid = 'F3FF80BA-BA05-4277-8063-82A6DB9245A2'
app = QtSingleApplication(appGuid, sys.argv)
if app.isRunning(): sys.exit(0)
w = QWidget()
w.show()
app.setActivationWindow(w)
sys.exit(app.exec_())

You can have a look to this blog entry. It is for Pyside but I guess that it will work too with PyQt4.

Related

How to make WPF MVVM Bindings work in python.NET (Cpython)?

I'm trying to port this IronPython WPF example to CPython with python.NET.
I'm able to get the window running after fixing some known issues (adding __namespace__ to the ViewModel class, using Single Thread Apartment and updating the syntax of pyevent.py to python3), but bindings are ignored: I can write in the textbox, but no events gets triggered, and the button doesn't fire the OnClick method.
Here's the full code:
import clr
clr.AddReference(r'wpf\PresentationFramework')
clr.AddReference('PresentationCore')
from System.Windows.Markup import XamlReader
from System.Windows import Application, Window
from System.ComponentModel import INotifyPropertyChanged, PropertyChangedEventArgs
import pyevent
from System.Threading import Thread, ThreadStart, ApartmentState
XAML_str = """<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Sync Paramanager" Height="180" Width="400">
<StackPanel x:Name="DataPanel" Orientation="Horizontal">
<Label Content="Size"/>
<Label Content="{Binding size}"/>
<TextBox x:Name="tbSize" Text="{Binding size, UpdateSourceTrigger=PropertyChanged}" />
<Button x:Name="Button" Content="Set Initial Value"></Button>
</StackPanel>
</Window>"""
class notify_property(property):
def __init__(self, getter):
def newgetter(slf):
#return None when the property does not exist yet
try:
return getter(slf)
except AttributeError:
return None
super().__init__(newgetter)
def setter(self, setter):
def newsetter(slf, newvalue):
# do not change value if the new value is the same
# trigger PropertyChanged event when value changes
oldvalue = self.fget(slf)
if oldvalue != newvalue:
setter(slf, newvalue)
slf.OnPropertyChanged(setter.__name__)
return property(
fget=self.fget,
fset=newsetter,
fdel=self.fdel,
doc=self.__doc__)
class NotifyPropertyChangedBase(INotifyPropertyChanged):
__namespace__ = "NotifyPropertyChangedBase"
PropertyChanged = None
def __init__(self):
self.PropertyChanged, self._propertyChangedCaller = pyevent.make_event()
def add_PropertyChanged(self, value):
self.PropertyChanged += value
def remove_PropertyChanged(self, value):
self.PropertyChanged -= value
def OnPropertyChanged(self, propertyName):
if self.PropertyChanged is not None:
self._propertyChangedCaller(self, PropertyChangedEventArgs(propertyName))
class ViewModel(NotifyPropertyChangedBase):
__namespace__ = "WpfViewModel"
def __init__(self):
super(ViewModel, self).__init__()
# must be string to two-way binding work correctly
self.size = '10'
#notify_property
def size(self):
return self._size
#size.setter
def size(self, value):
self._size = value
print(f'Size changed to {self.size}')
class TestWPF(object):
def __init__(self):
self._vm = ViewModel()
self.root = XamlReader.Parse(XAML_str)
self.DataPanel.DataContext = self._vm
self.Button.Click += self.OnClick
def OnClick(self, sender, event):
# must be string to two-way binding work correctly
self._vm.size = '10'
def __getattr__(self, name):
# provides easy access to XAML elements (e.g. self.Button)
return self.root.FindName(name)
def main():
tw = TestWPF()
app = Application()
app.Run(tw.root)
if __name__ == '__main__':
thread = Thread(ThreadStart(main))
thread.SetApartmentState(ApartmentState.STA)
thread.Start()
thread.Join()
It seems that assigning the ModelView to DataPanel's DataContext doesn't trigger any binding registration, but I have no clue on how to fix that.
Am I missing something obvious?
Unfortunately pythonnet does not support defining binding in the xaml file.

Show completion list when qlineEdit textChanged

I can use the following command to show an auto completion list view if I have a predefined list
wordList =["alpha", "omega", "omicron", "zeta"]
completer = QCompleter(wordList)
ineEdit.setCompleter(completer)
I need a different case:, Type something in lineEdit, when textChanged it connect to a function def searchAction: via lineEdit.textChanged.connect(searchAction). Inside def searchAction: i have an sql query which updates a list (say data_list) for every string I type in the lineEdit . If set wordList as data_list it does not do anything. My question how can I show the data_list as a autocompletion something like in the image.
It is not necessary to create lists since QCompleter supports models so you can filter using QSqlQueryModel as shown below:
import os
import sys
from pathlib import Path
from PyQt5.QtWidgets import QApplication, QCompleter, QLineEdit
from PyQt5.QtSql import QSqlDatabase, QSqlQuery, QSqlQueryModel
CURRENT_DIRECTORY = Path(__file__).resolve().parent
def create_connection(path):
db = QSqlDatabase.addDatabase("QSQLITE")
db.setDatabaseName(path)
if not db.open():
print(db.lastError().text())
return False
return True
class SqlQueryCompleter(QCompleter):
def __init__(self, sql_model=None, parent=None):
super().__init__(sql_model, parent)
if sql_model is None:
sql_model = QSqlQueryModel(self)
elif not isinstance(sql_model, QSqlQueryModel):
raise ValueError(f"The {sql_model} must be an instance of QSqlQueryModel")
self.setModel(sql_model)
self.setCompletionMode(QCompleter.UnfilteredPopupCompletion)
def splitPath(self, path):
if isinstance(self.model(), QSqlQueryModel):
self.model().setQuery(self.query(path))
return super().splitPath(path)
def query(self, path):
query = QSqlQuery("SELECT bar FROM 'foo' WHERE bar LIKE ?")
query.addBindValue(f"%{path}%")
if not query.exec_():
print(query.lastError().text())
return query
def main():
app = QApplication(sys.argv)
filename = os.fspath(CURRENT_DIRECTORY / "database.db")
if not create_connection(filename):
sys.exit(-1)
line_edit = QLineEdit()
line_edit.show()
completer = SqlQueryCompleter()
line_edit.setCompleter(completer)
sys.exit(app.exec_())
if __name__ == "__main__":
main()

How to reimplement QTextDocument createObject?

How to reimplement QTextDocument.createObject?
this method plays a role in making QTextFrame, QTextList, QTextTable or other QTextObject.
According to woboq, I think my reimplementation is the same.
But kernel stops.
Why? What is my code short of?
from PySide2 import QtWidgets
from PySide2 import QtGui
from PySide2 import QtCore
import PySide2
import sys
import os
dirname = os.path.dirname(PySide2.__file__)
plugin_path = os.path.join(dirname, 'plugins', 'platforms')
os.environ['QT_QPA_PLATFORM_PLUGIN_PATH'] = plugin_path
class TextEdit(QtWidgets.QTextEdit):
def __init__(self, parent=None):
super(TextEdit, self).__init__(parent=None)
document = TextDocument(self)
self.setDocument(document)
class TextDocument(QtGui.QTextDocument):
def __init__(self, parent=None):
super(TextDocument, self).__init__(parent=None)
self.setParent(parent)
def createObject(self, f):
obj = QtGui.QTextObject(self)
if f.isListFormat():
obj = QtGui.QTextList(self)
elif f.isTableFormat():
obj = QtGui.QTextTable(self)
elif f.isFrameFormat():
obj = QtGui.QTextFrame(self)
return obj
def main():
if QtWidgets.QApplication.instance() is not None:
app = QtWidgets.QApplication.instance()
else:
app = QtWidgets.QApplication([])
mainwindow = TextEdit()
mainwindow.show()
sys.exit(QtWidgets.QApplication.exec_())
if __name__ == "__main__":
main()
It seems to me that it is a bug (I have tested it with PyQt5 and it works correctly), the problem to be the life cycle of the QTextObject since as in C++ the life cycle is undefined since it is a pointer but being a child of QTextDocument So its life cycle is that of the QTextDocument, but in python it seems that it considers it an object of limited scope(local variable) not respecting the ownership that the QTextDocument has over this since it is its parent. A workaround seems to be making obj an member of the class:
def createObject(self, f):
self.obj = QtGui.QTextObject(self)
if f.isListFormat():
self.obj = QtGui.QTextList(self)
elif f.isTableFormat():
self.obj = QtGui.QTextTable(self)
elif f.isFrameFormat():
self.obj = QtGui.QTextFrame(self)
return self.obj
Or using a container that is a member of the class.
class TextDocument(QtGui.QTextDocument):
def __init__(self, parent=None):
super(TextDocument, self).__init__(parent)
self.objs = []
def createObject(self, f):
obj = QtGui.QTextObject(self)
if f.isListFormat():
obj = QtGui.QTextList(self)
elif f.isTableFormat():
obj = QtGui.QTextTable(self)
elif f.isFrameFormat():
obj = QtGui.QTextFrame(self)
self.objs.append(obj)
return obj
I prefer the second workaround since in the case of the first one you could generate problems if you create several QTextObject since the previous one would be deleted.
Finally I recommend reporting the bug.
The handling of the life cycle of some objects seems a persistent bug in PySide2.

QCheckBox state change PyQt4

I'm trying to implement a system in PyQt4 where unchecking a checkbox would call function disable_mod and checking it would call enable_mod. But even though state is changing the checkboxes call the initial function they started with. For this case if an already checked box was clicked it'd always keep calling the disable_mod function! I don't understand why is this happening? Can you guys help me out here a little bit? Here's my code:
from PyQt4 import QtCore, QtGui
from os import walk
from os.path import join
import sys
def list_files_regex(dir):
l = []
for (root, dirnames, filenames) in walk(dir):
for d in dirnames:
list_files_regex(join(root, d))
l.extend(filenames)
return l
directory = "J:/test_pack"
directory = join(directory, "/mods")
count = 0
for y in list_files_regex(directory):
print y
count += 1
print count
class ModEdit(QtGui.QMainWindow):
def __init__(self, title, icon, x, y, w, h):
super(ModEdit, self).__init__()
self.setWindowTitle(title)
self.setWindowIcon(QtGui.QIcon(icon))
self.setGeometry(x, y, w, h)
self.choices = []
self.init()
def init(self):
scroll_widget = QtGui.QScrollArea()
sub_widget = QtGui.QWidget()
v_layout = QtGui.QVBoxLayout()
for y in list_files_regex(directory):
tmp = QtGui.QCheckBox(y, self)
tmp.resize(tmp.sizeHint())
if tmp.text()[len(tmp.text()) - 8: len(tmp.text())] != 'disabled':
tmp.setChecked(True)
# if tmp.isChecked() == 0:
# tmp.stateChanged.connect(self.enable_mod)
# if tmp.isChecked():
# tmp.stateChanged.connect(self.disable_mod)
# v_layout.addWidget(tmp)
self.choices.append(tmp)
print self.choices
for choice in self.choices:
v_layout.addWidget(choice)
if choice.isChecked():
choice.stateChanged.connect(self.disable_mod)
else:
choice.stateChanged.connect(self.enable_mod)
sub_widget.setLayout(v_layout)
scroll_widget.setWidget(sub_widget)
self.setCentralWidget(scroll_widget)
self.show()
def enable_mod(self):
print "ENABLE_TEST"
print self.choices[1].isChecked()
def disable_mod(self):
print "DISABLE_TEST"
print self.choices[1].isChecked()
def test(self):
print 'test'
for ch in self.choices:
if ch.isChecked():
ch.stateChanged.connect(self.disable_mod)
else:
ch.stateChanged.connect(self.enable_mod)
class Rename(QtCore.QObject):
enable = QtCore.pyqtSignal
disable = QtCore.pyqtSignal
app = QtGui.QApplication(sys.argv)
ex = ModEdit("Minecraft ModEdit", "ModEdit.png", 64, 64, 640, 480)
sys.exit(app.exec_())
The problem is that you're only connecting the checkbox stateChanged signal once during initialization. After the state of the checkbox changes, you're not disconnecting the signal and reconnecting it to the correct slot.
You'll need to connect the stateChanged signal to an intermediary slot that will decide which function to call based on the checkbox state. Since you're using the same slot for multiple checkboxes, it's probably best to also pass the checkbox to the slot as well.
from functools import partial
def init(self):
...
for tmp in list_of_checkboxes:
enable_slot = partial(self.enable_mod, tmp)
disable_slot = partial(self.disable_mod, tmp)
tmp.stateChanged.connect(lambda x: enable_slot() if x else disable_slot())
def enable_mod(self, checkbox):
print "ENABLE_TEST"
print checkbox.isChecked()
def disable_mod(self, checkbox):
print "DISABLE_TEST"
print checkbox.isChecked()
Alternatively, since we are now passing the checkbox to the slots, you could just use a single slot and check the checkbox state inside the slot
def init(self):
...
for tmp in list_of_checkboxes:
slot = partial(self.enable_disable_mod, tmp)
tmp.stateChanged.connect(lambda x: slot())
def enable_disable_mod(self, checkbox):
if checkbox.isChecked():
...
else:
...

NSMenuItem + Python + Dynamic Variables

I'm trying to get a dynamic label for a menu item in this. I've written the entire app in Python but honestly with how much NSMenuItem looks, I might as well rewrite it in Objc...
import objc
from Foundation import *
from AppKit import *
from PyObjCTools import AppHelper
class MyApp(NSApplication):
def finishLaunching(self):
# Make statusbar item
statusbar = NSStatusBar.systemStatusBar()
self.statusitem = statusbar.statusItemWithLength_(NSVariableStatusItemLength)
self.icon = NSImage.alloc().initByReferencingFile_('icon.png')
self.icon.setScalesWhenResized_(True)
self.icon.setSize_((20, 20))
self.statusitem.setImage_(self.icon)
#make the menu
self.menubarMenu = NSMenu.alloc().init()
self.menuItem = NSMenuItem.alloc().initWithTitle_action_keyEquivalent_('Click Me', 'clicked:', '')
self.menubarMenu.addItem_(self.menuItem)
self.quit = NSMenuItem.alloc().initWithTitle_action_keyEquivalent_('Quit', 'terminate:', '')
self.menubarMenu.addItem_(self.quit)
#add menu to statusitem
self.statusitem.setMenu_(self.menubarMenu)
self.statusitem.setToolTip_('My App')
def clicked_(self, notification):
NSLog('clicked!')
if __name__ == "__main__":
app = MyApp.sharedApplication()
AppHelper.runEventLoop()
Did you try setTitle_?
def clicked_(self, notification):
self.menuItem.setTitle_("Clicked!")
or with a timer:
def finishLaunching(self):
# ...
self.timer = NSTimer.alloc().initWithFireDate_interval_target_selector_userInfo_repeats_(NSDate.date(), 1.0, self, 'tick:', None, True)
NSRunLoop.currentRunLoop().addTimer_forMode_(self.timer, NSDefaultRunLoopMode)
self.timer.fire()
def tick_(self, arg):
self.menuItem.setTitle_("Tick %d!" % int(time.time()))
For live updates you probably need JGMenuWindow (SO: How to update NSMenu while it's open?)

Categories