I've implemented a very simple log viewer in Python using PyQt4.
I am interested in using it to follow the execution of a program, so the list view has to be refreshed when a new line is appended to the log file.
Here is my implementation (without the watch):
import sys
from PyQt4.QtCore import *
from PyQt4.QtGui import *
class LogEntryModel(QAbstractListModel):
def __init__(self, logfile, parent=None):
super(LogEntryModel, self).__init__(parent)
self.slurp(logfile)
def rowCount(self, parent=QModelIndex()):
return len(self.entries)
def data(self, index, role):
if index.isValid() and role == Qt.DisplayRole:
return QVariant(self.entries[index.row()])
else:
return QVariant()
def slurp(self, logfile):
self.entries = []
with open(logfile, 'rb') as fp:
for line in fp.readlines():
tokens = line.strip().split(' : ')
sender = tokens[2]
message = tokens[4]
entry = "%s %s" % (sender, message)
self.entries.append(entry)
class LogViewerForm(QDialog):
def __init__(self, logfile, parent=None):
super(LogViewerForm, self).__init__(parent)
# build the list widget
list_label = QLabel(QString("<strong>MoMo</strong> Log Viewer"))
list_model = LogEntryModel(logfile)
self.list_view = QListView()
self.list_view.setModel(list_model)
list_label.setBuddy(self.list_view)
# define the layout
layout = QVBoxLayout()
layout.addWidget(list_label)
layout.addWidget(self.list_view)
self.setLayout(layout)
if __name__ == "__main__":
app = QApplication(sys.argv)
form = LogViewerForm(sys.argv[1])
form.show()
app.exec_()
As presented, the application works as expected: open the file, parse the contents (split at ' : ' and create a list), and display the list using a QListView.
There is a QFileSystemWatcher class which emits a fileChanged signal, but I don't know where to connect it and how to trigger an add a row to the data and refresh the view event.
Any help?
Thanks.
I am quite new to python and pyqt, but this "works" here:
import sys
from PyQt4.QtCore import *
from PyQt4.QtGui import *
class LogEntryModel(QAbstractListModel):
def __init__(self, logfile, parent=None):
super(LogEntryModel, self).__init__(parent)
self.slurp(logfile)
self.logfile = logfile
def rowCount(self, parent=QModelIndex()):
return len(self.entries)
def data(self, index, role):
if index.isValid() and role == Qt.DisplayRole:
return QVariant(self.entries[index.row()])
else:
return QVariant()
def slurp(self, logfile):
self.entries = []
with open(logfile, 'rb') as fp:
for line in fp.readlines():
tokens = line.strip().split(' : ')
sender = tokens[2]
message = tokens[4]
entry = "%s %s" % (sender, message)
self.entries.append(entry)
class LogViewerForm(QDialog):
def __init__(self, logfile, parent=None):
super(LogViewerForm, self).__init__(parent)
self.watcher = QFileSystemWatcher([logfile], parent=None)
self.connect(self.watcher, SIGNAL('fileChanged(const QString&)'), self.update_log)
# build the list widget
list_label = QLabel(QString("<strong>MoMo</strong> Log Viewer"))
list_model = LogEntryModel(logfile)
self.list_model = list_model
self.list_view = QListView()
self.list_view.setModel(self.list_model)
list_label.setBuddy(self.list_view)
# define the layout
layout = QVBoxLayout()
layout.addWidget(list_label)
layout.addWidget(self.list_view)
self.setLayout(layout)
def update_log(self):
print 'file changed'
self.list_model.slurp(self.list_model.logfile)
self.list_view.updateGeometries()
if __name__ == "__main__":
app = QApplication(sys.argv)
form = LogViewerForm(sys.argv[1])
form.show()
app.exec_()
But be aware that this is probably not a good way to do it.
You might want to stream the logfile...
Maybe somebody more experienced can help out.
Related
I built a google search suggestion completer for my simple browser built with pyqt5 and python. code is below.
self.url_bar = QLineEdit()
self.url_bar.textChanged.connect(self.suggest)
def suggest(self):
suggestlist =[]
term = self.url_bar.text()
if not (validators.url(term) and term != '') :
url = f"http://toolbarqueries.google.com/complete/search?q={term}&output=toolbar&hl=en"
suggestions = requests.get(url)
suggestions = ET.fromstring(suggestions.content)
for data in suggestions.iter('suggestion'):
suggestlist.append(data.attrib['data'])
suggester = QCompleter(suggestlist)
self.url_bar.setCompleter(suggester)
this generates the suggestions correctly but browser is accidentially closed. please help me.
Based on my previous post and making some modifications you can have the following solution. I don't use requests as it potentially blocks the eventloop if the request takes too long.
import xml.etree.ElementTree as ET
from PyQt5 import QtCore, QtGui, QtWidgets, QtNetwork
class SuggestionModel(QtGui.QStandardItemModel):
finished = QtCore.pyqtSignal()
error = QtCore.pyqtSignal(str)
def __init__(self, parent=None):
super(SuggestionModel, self).__init__(parent)
self._manager = QtNetwork.QNetworkAccessManager(self)
self._reply = None
#QtCore.pyqtSlot(str)
def search(self, text):
self.clear()
if self._reply is not None:
self._reply.abort()
if text:
r = self.create_request(text)
self._reply = self._manager.get(r)
self._reply.finished.connect(self.on_finished)
loop = QtCore.QEventLoop()
self.finished.connect(loop.quit)
loop.exec_()
def create_request(self, text):
url = QtCore.QUrl("http://toolbarqueries.google.com/complete/search")
query = QtCore.QUrlQuery()
query.addQueryItem("q", text)
query.addQueryItem("output", "toolbar")
query.addQueryItem("hl", "en")
url.setQuery(query)
request = QtNetwork.QNetworkRequest(url)
return request
#QtCore.pyqtSlot()
def on_finished(self):
reply = self.sender()
if reply.error() == QtNetwork.QNetworkReply.NoError:
content = reply.readAll().data()
suggestions = ET.fromstring(content)
for data in suggestions.iter("suggestion"):
suggestion = data.attrib["data"]
self.appendRow(QtGui.QStandardItem(suggestion))
self.error.emit("")
elif reply.error() != QtNetwork.QNetworkReply.OperationCanceledError:
self.error.emit(reply.errorString())
else:
self.error.emit("")
self.finished.emit()
reply.deleteLater()
self._reply = None
class Completer(QtWidgets.QCompleter):
def splitPath(self, path):
self.model().search(path)
return super(Completer, self).splitPath(path)
class Widget(QtWidgets.QWidget):
def __init__(self, parent=None):
super(Widget, self).__init__(parent)
self._model = SuggestionModel(self)
completer = Completer(self, caseSensitivity=QtCore.Qt.CaseInsensitive)
completer.setModel(self._model)
lineedit = QtWidgets.QLineEdit()
lineedit.setCompleter(completer)
label = QtWidgets.QLabel()
self._model.error.connect(label.setText)
lay = QtWidgets.QFormLayout(self)
lay.addRow("Location: ", lineedit)
lay.addRow("Error: ", label)
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
w = Widget()
w.resize(400, w.sizeHint().height())
w.show()
sys.exit(app.exec_())
class MyCompleter(QCompleter):
insertText = QtCore.pyqtSignal(str)
def __init__(self, parent=None):
keywords_list = ['open browser', 'click', 'input text', 'contain', 'log', 'clear',
'clearcookies', 'doubleclick', 'scrol to view', 'should', 'reload',
'and', 'wait', 'children', 'right click', '.rightclick()', 'read file'
, 'equal']
crntDir = "D:\\log"
image_list = []
philes = os.listdir(crntDir)
for phile in philes:
if phile.endswith(".png"):
image_list.append(phile)
print(image_list)
#image name in image_list
QCompleter.__init__(self,keywords_list, parent)
self.setCompletionMode(QCompleter.PopupCompletion)
QCompleter.__init__(self,image_list, parent)
self.highlighted.connect(self.setHighlighted)
def setHighlighted(self, text):
self.lastSelected = text
def getSelected(self):
return self.lastSelected
I want to show two different word list, keyword_list for simple word complete, image_list is get all image names in assigned folder. If I input image name, then complete show image full name *.png.
import sys
import os
import qdarkstyle
from PyQt5 import QtGui, QtCore,QtWidgets
from PyQt5 import QtWidgets
from PyQt5.QtWidgets import *
class MyListModel(QtCore.QAbstractListModel):
def __init__(self, datain, parent=None, *args):
QtCore.QAbstractListModel.__init__(self, parent, *args)
self.listdata = datain
def rowCount(self, parent=QtCore.QModelIndex()):
return len(self.listdata)
def data(self, index, role):
s = QtCore.QSize(250, 200)
if index.isValid() and role == QtCore.Qt.DecorationRole:
return QtGui.QIcon(QtGui.QPixmap(self.listdata[index.row()]))
if index.isValid() and role == QtCore.Qt.DisplayRole:
# print(QtCore.QVariant(self.ListItemData[index.row()]['name']))
return QtCore.QVariant(os.path.splitext(os.path.split(self.listdata[index.row()])[-1])[0])
else:
return QtCore.QVariant()
def getItem(self, index):
if index > -1 and index < len(self.ListItemData):
return self.ListItemData[index]
class MyListView(QtWidgets.QListView):
def __init__(self,parent=None):
super(MyListView, self).__init__(parent)
self.Listview = QListView()
self.setViewMode(QtWidgets.QListView.IconMode)
self.setIconSize(QtCore.QSize(90, 90))
self.setGridSize(QtCore.QSize(110, 110))
crntDir = "D:\\log"
list_data = []
image_list = []
philes = os.listdir(crntDir)
for phile in philes:
if phile.endswith(".png"):
list_data.append(os.path.join(crntDir, phile))
image_list.append(phile)
self.List_data = list_data
lm = MyListModel(list_data)
self.setModel(lm)
self.show()
self.clicked.connect(self.onclicked)
def onclicked(self,item):
image_selected_path = self.List_data[item.row()]
print(image_selected_path)
self.close()
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
window = MyListView()
window.setWindowOpacity(0.9)
window.show()
window.raise_()
sys.exit(app.exec_())
I want image display in this window to show matched image name. How to join this two function
I'm looking for help regarding how to add a QTableView inside a QGroupBox (this is because i need to create 4 QTableView each displaying one of each possible Status 'In QC','Ready for QC','In Progress','Pending').
The following code currently generates a program that displays a Single QTableView, that refreshes every 5 seconds with new data, the only one that matters is the Status (currently represented as column F) as the rest of the data is displayed for identification purposes. (Please note that in this example i use a code that generates automatically data to display in the QTableView, as this Table actually feeds from an Excel file, going to attach the code that reads the excel file at the end of this post):
import sys
import pandas as pd
from PyQt5.QtCore import pyqtSignal, pyqtSlot, QAbstractTableModel, QObject, Qt
from PyQt5.QtGui import QBrush
from PyQt5.QtWidgets import QApplication, QTableView
import threading
class PandasManager(QObject):
dataFrameChanged = pyqtSignal(pd.DataFrame)
def start(self):
self.t = threading.Timer(0, self.load)
self.t.start()
def load(self):
import random
headers = list("ABCDEFG")
data = [random.sample(range(255), len(headers)) for _ in headers]
for d in data:
d[5] = random.choice(["Ready for QC", "In Progress", "Pending", "In QC"])
df = pd.DataFrame(data, columns=headers,)
self.dataFrameChanged.emit(df)
self.t = threading.Timer(5.0, self.load)
self.t.start()
def stop(self):
self.t.cancel()
class PandasModel(QAbstractTableModel):
def __init__(self, df=pd.DataFrame()):
QAbstractTableModel.__init__(self)
self._df = df
#pyqtSlot(pd.DataFrame)
def setDataFrame(self, df):
self.beginResetModel()
self._df = df
self.endResetModel()
def rowCount(self, parent=None):
return self._df.shape[0]
def columnCount(self, parent=None):
return self._df.shape[1]
def data(self, index, role=Qt.DisplayRole):
if index.isValid():
if role == Qt.BackgroundRole:
if self.columnCount() >= 6:
it = self._df.iloc[index.row(), 5]
if it == "Ready for QC":
return QBrush(Qt.yellow)
if it == "In Progress":
return QBrush(Qt.green)
if role == Qt.DisplayRole:
return str(self._df.iloc[index.row(), index.column()])
def headerData(self, col, orientation, role):
if orientation == Qt.Horizontal and role == Qt.DisplayRole:
return self._df.columns[col]
return None
if __name__ == "__main__":
app = QApplication(sys.argv)
w = QTableView()
model = PandasModel()
w.setModel(model)
w.show()
manager = PandasManager()
manager.dataFrameChanged.connect(model.setDataFrame)
manager.start()
ret = app.exec_()
manager.stop()
sys.exit(ret)
Hopefully this explains my question, as i have been struggling on how to use QGroupBox and how to add the QTableView as I'm using it this way.
Kind Regards,
PS: Attaching code that reads from an excel file
def load(self):
weekNumber = date.today().isocalendar()[1]
aux = pd.read_excel("PCS tasks 2020.xlsm", sheet_name="W" + str(weekNumber))
today = datetime.today()
df = aux[aux["Date Received"] == today.strftime("%Y-%d-%m")]
df = df[
[
"Requestor",
"Subject",
"Task type",
"Created by",
"QC Executive",
"Status",
]
].fillna("")
df = df[df["Status"] != "Completed"]
self.dataFrameChanged.emit(df)
self.t = threading.Timer(5.0, self.load)
self.t.start()
As the example of the QGroupBox docs shows, you must use a layout that allows you to distribute the widgets (QGridLayout, QHBoxLayout, QVBoxLayout, etc.) and set it in the QGroupBox:
if __name__ == "__main__":
app = QApplication(sys.argv)
w = QTableView()
model = PandasModel()
w.setModel(model)
groupbox = QGroupBox()
lay = QVBoxLayout()
lay.addWidget(w)
groupbox.setLayout(lay)
groupbox.show()
manager = PandasManager()
manager.dataFrameChanged.connect(model.setDataFrame)
manager.start()
ret = app.exec_()
manager.stop()
sys.exit(ret)
How do you put the fullname (from notepad user; pass; fullname) to show at the Qlineedit widget in the second class (which is also the second GUI)
FIRST CLASS
from inventoryform import InventoryDialog
class LoginDialog(QtWidgets.QDialog, Ui_loginform):
isLogged = QtCore.pyqtSignal()
def __init__(self, parent=None):
def login_button_clicked(self):
import csv
with open('user.txt', newline='') as f:
reader = csv.reader(f, delimiter=';')
for line in reader:
us, pa, fn = line
if self.username_line.text() == us and self.password_line.text() == pa:
QtWidgets.QMessageBox.information(self, "LOGIN", "LOGIN SUCCESSFUL!")
self.isLogged.emit()
a = QLineEdit()
a.setText(fn)
usershowname_line.append(a)
self.close()
return
else:
QtWidgets.QMessageBox.information(self, "LOGIN FAILED", "LOGIN FAILED!")
def exit_button_clicked(self):
self.close()
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
loginform_window = LoginDialog()
x = InventoryDialog()
loginform_window.isLogged.connect(x.show)
x.isLogout.connect(loginform_window.show)
loginform_window.show()
sys.exit(app.exec_())
SECOND CLASS
class InventoryDialog(QtWidgets.QDialog, Ui_inventoryform):
isLogout = QtCore.pyqtSignal()
def __init__(self, parent=None):
QtWidgets.QDialog.__init__(self, parent)
self.setupUi(self)
self.signout_button.clicked.connect(self.onSignout_button)
def onSignout_button(self):
self.isLogout.emit()
self.close()
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
inventoryform_window = InventoryDialog()
inventoryform_window.show()
sys.exit(app.exec_())
Here's the QlineEdit which object name is usershowname_line from the second gui:
You have to create a signal that can send a string (pyqtSignal (str)), this will emit before closing the window. To show it we must connect it to setText of QLineEdit (In your case it replaces your_lineedit with the name of your QLineEdit). All of the above is shown in the following part:
class LoginDialog(QtWidgets.QDialog, Ui_loginform):
isLogged = QtCore.pyqtSignal()
dataSignal = QtCore.pyqtSignal(str)
[...]
def login_button_clicked(self):
[...]
self.dataSignal.emit("user: {}, pass: {}, fullname: {}".format(us, pa, fn))
self.isLogged.emit()
[...]
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
loginform_window = LoginDialog()
x = InventoryDialog()
loginform_window.isLogged.connect(x.show)
loginform_window.dataSignal.connect(x.your_lineedit.setText)
# ^^^^^^^^^^^^^
x.isLogout.connect(loginform_window.show)
loginform_window.show()
sys.exit(app.exec_())
I am fairly new to PyQt, I'm working on a project that contains a QTableView, with one of its columns displaying system paths. I would like to add a QTreeView so users can click the + or > buttons to expand what is underneath the paths.
Here is my basic implementation:
from PyQt4 import QtGui
from PyQt4 import QtCore
class MainWindow(QtGui.QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.resize(600,400)
self.setWindowTitle("My Basic Treeview")
self.treeview = QtGui.QTreeView(self)
self.treeview.model = QtGui.QFileSystemModel()
self.treeview.model.setRootPath('/opt')
self.treeview.setModel(self.treeview.model)
self.treeview.setColumnWidth(0, 200)
self.setCentralWidget(self.treeview)
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(app.exec_())
Although, in the above case, I get all folders but I just want the /opt path and its underneath folders.
import operator
from PyQt4.QtCore import *
from PyQt4.QtGui import *
class MyWindow(QWidget):
def __init__(self, data_list, header, *args):
QWidget.__init__(self, *args)
# setGeometry(x_pos, y_pos, width, height)
self.setGeometry(300, 200, 570, 450)
self.setWindowTitle("Click on column title to sort")
table_model = MyTableModel(self, data_list, header)
table_view = QTableView()
table_view.setModel(table_model)
# set font
font = QFont("Courier New", 14)
table_view.setFont(font)
# set column width to fit contents (set font first!)
table_view.resizeColumnsToContents()
# enable sorting
table_view.setSortingEnabled(True)
layout = QVBoxLayout(self)
layout.addWidget(table_view)
self.setLayout(layout)
class MyTableModel(QAbstractTableModel):
def __init__(self, parent, mylist, header, *args):
QAbstractTableModel.__init__(self, parent, *args)
self.mylist = mylist
self.header = header
def rowCount(self, parent):
return len(self.mylist)
def columnCount(self, parent):
return len(self.mylist[0])
def data(self, index, role):
if not index.isValid():
return None
elif role != Qt.DisplayRole:
return None
return self.mylist[index.row()][index.column()]
def headerData(self, col, orientation, role):
if orientation == Qt.Horizontal and role == Qt.DisplayRole:
return self.header[col]
return None
# the solvent data ...
header = ['Name', ' Email', ' Status', ' Path']
# use numbers for numeric data to sort properly
data_list = [
('option_A', 'zyro#email.com', 'Not Copied', '/Opt'),
('option_B', 'zyro#email.com', 'Not Copied', '/Users'),
]
app = QApplication([])
win = MyWindow(data_list, header)
win.show()
app.exec_()
Visual example :
I think your question can be divided in two parts:
how, in a QTreeView, the /opt path and its children can be shown, but without showing its siblings. In other words, how is it possible to show the root directory in a QTreeView ;
how can a QTreeView be added to a QTableView.
1. How to include the root directory in a QTreeView :
The root of a QTreeView is the directory for which the content is shown in the view. It is set when calling the method setRootIndex. According to a post by wysota on Qt Centre:
You can't display the invisibleRootItem because it is a fake item used only to have an equivalent of empty QModelIndex.
A workaround would be to set the root directory to the parent of /opt and filtering out the siblings of /opt with a subclass of a QSortFilterProxyModel. Note that I've also reimplemented the sizeHint method which will be necessary for the resizing of the rows of the QTableView:
from PyQt4 import QtGui, QtCore
import os
class MyQTreeView(QtGui.QTreeView):
def __init__(self, path, parent=None):
super(MyQTreeView, self).__init__(parent)
ppath = os.path.dirname(path) # parent of path
self.setFrameStyle(0)
#---- File System Model ----
sourceModel = QtGui.QFileSystemModel()
sourceModel.setRootPath(ppath)
#---- Filter Proxy Model ----
proxyModel = MyQSortFilterProxyModel(path)
proxyModel.setSourceModel(sourceModel)
#---- Filter Proxy Model ----
self.setModel(proxyModel)
self.setHeaderHidden(True)
self.setRootIndex(proxyModel.mapFromSource(sourceModel.index(ppath)))
#--- Hide All Header Sections Except First ----
header = self.header()
for sec in range(1, header.count()):
header.setSectionHidden(sec, True)
def sizeHint(self):
baseSize = super(MyQTreeView,self).sizeHint()
#---- get model index of "path" ----
qindx = self.rootIndex().child(0, 0)
if self.isExpanded(qindx): # default baseSize height will be used
pass
else: # shrink baseShize height to the height of the row
baseSize.setHeight(self.rowHeight(qindx))
return baseSize
class MyQSortFilterProxyModel(QtGui.QSortFilterProxyModel):
def __init__(self, path, parent=None):
super(MyQSortFilterProxyModel, self).__init__(parent)
self.path = path
def filterAcceptsRow(self, row, parent):
model = self.sourceModel()
path_dta = model.index(self.path).data()
ppath_dta = model.index(os.path.dirname(self.path)).data()
if parent.data() == ppath_dta:
if parent.child(row, 0).data() == path_dta:
return True
else:
return False
else:
return True
2. How to add a *QTreeView* to a *QTableView* :
It is possible to add a QTreeView to a QTableView by using a QItemDelegate. The post by Pavel Strakhov greatly helped me for this, since I had never used QTableView in combination with delegates before answering to this question. I always used QTableWidget instead with the setCellWidget method.
Note that I've setup a signal in the MyDelegate class which call the method resizeRowsToContents in the MyTableView class. This way, the height of the rows resize according the the reimplementation of the sizeHint method of the MyQTreeView class.
class MyTableModel(QtCore.QAbstractTableModel):
def __init__(self, parent, mylist, header, *args):
super(MyTableModel, self).__init__(parent, *args)
self.mylist = mylist
self.header = header
def rowCount(self, parent=QtCore.QModelIndex()):
return len(self.mylist)
def columnCount(self, parent=QtCore.QModelIndex()):
return len(self.mylist[0])
def data(self, index, role):
if not index.isValid():
return None
elif role != QtCore.Qt.DisplayRole:
return None
return self.mylist[index.row()][index.column()]
def headerData(self, col, orientation, role):
if orientation == QtCore.Qt.Horizontal and role == QtCore.Qt.DisplayRole:
return self.header[col]
return None
class MyDelegate(QtGui.QItemDelegate):
treeViewHeightChanged = QtCore.pyqtSignal(QtGui.QWidget)
def createEditor(self, parent, option, index):
editor = MyQTreeView(index.data(), parent)
editor.collapsed.connect(self.sizeChanged)
editor.expanded.connect(self.sizeChanged)
return editor
def sizeChanged(self):
self.treeViewHeightChanged.emit(self.sender())
class MyTableView(QtGui.QTableView):
def __init__(self, data_list, header, *args):
super(MyTableView, self).__init__(*args)
#---- set up model ----
model = MyTableModel(self, data_list, header)
self.setModel(model)
#---- set up delegate in last column ----
delegate = MyDelegate()
self.setItemDelegateForColumn(3, delegate)
for row in range(model.rowCount()):
self.openPersistentEditor(model.index(row, 3))
#---- set up font and resize calls ----
self.setFont(QtGui.QFont("Courier New", 14))
self.resizeColumnsToContents()
delegate.treeViewHeightChanged.connect(self.resizeRowsToContents)
3. Basic application :
Here is a basic application based on the code you provided in your OP:
if __name__ == '__main__':
header = ['Name', ' Email', ' Status', ' Path']
data_list = [('option_A', 'zyro#email.com', 'Not Copied', '/opt'),
('option_B', 'zyro#email.com', 'Not Copied', '/usr')]
app = QtGui.QApplication([])
win = MyTableView(data_list, header)
win.setGeometry(300, 200, 570, 450)
win.show()
app.exec_()
Which results in: