How to save edited webview result? - python-3.7

Situation description:
Python 3.7, GTK 3.0, PyGObjects 3.34.0 Webkit2 4.0
I have a dialog window, with GtkNotebook containing 2 tabs.
1. tab contains editable Webkit webview, the 2. tab contains textview. One of the arguments provided in class consrtructor is valid HTML snippet as string variable
What I would like to get as a result, is that any changes made in any window, are automatically reflected in other.
Current problem:
Using solution provided here, any previous changes that were made in webview are discarded upon switching the notepad tabs. Debugging shows that html obtained with aforementioned call, does not contain changes.
Any ideas what might be missing in the logic or handling itself?
For reference, the code for the dialog is as follows:
# -*- coding: utf-8 -*-
import gi
gi.require_version('Gtk', '3.0')
gi.require_version('WebKit2', '4.0')
from gi.repository import Gtk, WebKit2
class DescriptionDialog:
def __init__(self, *args):
# GTK Builder
self._builder = args[0]
self._builder.add_from_file("UI/GUI/description.glade")
self.dialog = self._builder.get_object("descriptionDialog")
self._textView = self._builder.get_object("textview1")
self.webViewContainer = self._builder.get_object("WebViewContainer")
self.browserHolder = WebKit2.WebView()
self.browserHolder.set_editable(True)
self.webViewContainer.add(self.browserHolder)
self.browserHolder.show()
# valid html snippet, held as string
self.__buffer_orig__ = args[2]
self.buffer = args[2]
self.browserHolder.load_html(self.buffer)
self._builder.connect_signals(
{
"onDialogClose": self.onDialogClose,
"pageChangeNotebook": self.onPageChange
})
self.dialog.set_transient_for(self._builder.get_object("MainWindow"))
self.textBuffer = self._builder.get_object("textbuffer1")
self.textBuffer.set_text(self.buffer)
self.dialog.show()
def onDialogClose(self, handler):
self.dialog.hide()
def onPageChange(self, notebook=None, scrolledWindow=None, pageNumber=0):
if pageNumber == 0:
self.buffer = self.textBuffer.get_text(self.textBuffer.get_start_iter(), self.textBuffer.get_end_iter(), True)
self.browserHolder.load_html(self.buffer)
if pageNumber == 1:
self.browserHolder.get_main_resource().get_data(None, self.getDataFromResource, None)
def getDataFromResource(self, resource, result, userData=None):
# Changed html is not returned here
self.buffer = str(resource.get_data_finish(result).decode("utf-8"))
self.textBuffer.set_text(self.buffer)

For other internet users finding this thread.
Currently, at the given versions, this is the working result that I have come up with.
Main idea with this implementation is following - use the WebView enabled Javascript engine to obtain the contents of a <body> tag. Then, parse the Javascript result to use this value later on.
def onPageChange(self, notebook=None, scrolledWindow=None, pageNumber=0):
if pageNumber == 0:
self.buffer = self.textBuffer.get_text(self.textBuffer.get_start_iter(), self.textBuffer.get_end_iter(), True)
self.browserHolder.load_html(self.buffer)
if pageNumber == 1:
# use JavaScript to get the html contained in rendered body tag
script = "document.body.innerHTML;"
# Execute JavasScript via WebKit2.WebView bindings.
# Result can be obtained asynchronously, via callback method
self.browserHolder.run_javascript(script, None, self.getJSStatus, None)
def getJSStatus(self, resource, result, userData=None):
# Sample adapted and simplified from here:
# https://lazka.github.io/pgi-docs/#WebKit2-4.0/classes/WebView.html#WebKit2.WebView.run_javascript_finish
# Get the JavaScript result
data = self.browserHolder.run_javascript_finish(result)
# Get value from result, and convert it to string
self.buffer = data.get_js_value().to_string()
self.textBuffer.set_text(self.buffer)

Related

Trying to catch MsgBox text and press button in xlwings

So I have some code which uses xlwings for writing data in Excel file, xlsm.
after i've done writing, I press a certain button to calculate.
sometimes, an error/message pops in the Excel, which is fine, but i want to catch this message to python, and write it later to a log/print it.
also, i need to interact with this message, in this case to press "Ok" in the message box
Attached image of the message box
So guys, I've been able to solve this with an external python library.
here is the code:
from pywinauto import application as autoWin
app = autoWin.Application()
con = app.connect(title = 'Configuration Error')
msgText = con.Dialog.Static2.texts()[0]
con.Dialog.Button.click()
con.Dialog.Button.click()
print(msgText)
basically, what it does, is connecting to the app, and searching for the title.
in this case "Configuration Error"
it needs to perform double click in order to press "Ok" to close the message.
Secondly, it gets the text from the message, and can forward it wherever i want.
important part to remember though, because this should be an automated task, it should run concurrently, which means Threading.
so, a simple Thread class below:
class ButtonClicker(Thread):
def __init__(self):
Thread.__init__(self)
self._stop_event = Event()
def stop(self):
self._stop_event.set()
def stopped(self):
return self._stop_event.is_set()
def run(self) -> None:
while True:
time.sleep(3)
try:
app = autoWin.Application()
con = app.connect(title='Configuration Error')
msg_data = con.Dialog.Static2.texts()[0]
while True:
con.Dialog.Button.click()
# con.Dialog.Button.click()
# print(msg_data)
return msg_data
except Exception as e:
print('Excel didnt stuck')
break
and of course to actually use it:
event_handle = ButtonClicker()
event_handle.start()
some manipulation is needed in order to work in different codes/scenarios, but at least I hope i will help others in the future, because this seems to be very common question.
#Danny's solution, i.e. pywinauto and Thread, works perfectly in my local machine, but it seems can't catch the message box when Excel is running in server mode, e.g. in my case, the automation is triggered in local and started by a system service installed in the server.
pywinauto.findwindows.ElementNotFoundError:
{'title': '<my-wanted-title>', 'backend': 'win32', 'visible_only': False}
It is finally solved with another python third-party library pywin32, so providing a backup solution here.
'''
Keep finding message box with specified title and clicking button to close it,
until stopped by the main thread.
'''
import time
from threading import Thread, Event
import win32gui
import win32con
class ButtonClicker(Thread):
def __init__(self, title:str, interval:int):
Thread.__init__(self)
self._title = title
self._interval = interval
self._stop_event = Event()
def stop(self):
'''Stop thread.'''
self._stop_event.set()
#property
def stopped(self):
return self._stop_event.is_set()
def run(self):
while not self.stopped:
try:
time.sleep(self._interval)
self._close_msgbox()
except Exception as e:
print(e, flush=True)
def _close_msgbox(self):
# find the top window by title
hwnd = win32gui.FindWindow(None, self._title)
if not hwnd: return
# find child button
h_btn = win32gui.FindWindowEx(hwnd, None,'Button', None)
if not h_btn: return
# show text
text = win32gui.GetWindowText(h_btn)
print(text)
# click button
win32gui.PostMessage(h_btn, win32con.WM_LBUTTONDOWN, None, None)
time.sleep(0.2)
win32gui.PostMessage(h_btn, win32con.WM_LBUTTONUP, None, None)
time.sleep(0.2)
if __name__=='__main__':
t = ButtonClicker('Configuration Error', 3)
t.start()
time.sleep(10)
t.stop()

Metadata-Extractor -- Missing List of Tags?

I'm using metadata-extractor to retrieve metadata from video files. I have it successfully retrieving the directories. Now I need to query the directories for specific info -- duration, height, etc.
The metadata-extractor docs give this example of how to query for a specific tag value:
// obtain the Exif directory
ExifSubIFDDirectory directory
= metadata.getFirstDirectoryOfType(ExifSubIFDDirectory.class);
// query the tag's value
Date date
= directory.getDate(ExifSubIFDDirectory.TAG_DATETIME_ORIGINAL);
So it appears I need to get a list of the relevant tags, such as TAG_DATETIME_ORIGINAL, for duration, height, etc.
This page in the metadata-extractor docs contains a link titled "the various tag values", but the page it goes to lists tags for still images only, not for video files.
Googling for Metadata-Extractor -- Complete List of All Tags does not seem to bring up a list of all tags.
Are the metadata-extractor docs really missing a list of tags, or am I approaching this the wrong way somehow?
I found a list of tags at:
https://developer.tizen.org/dev-guide/2.3.1/org.tizen.guides/html/native/multimedia/metadata_extractor_n.htm
However, those constants don't seem to be what's needed in actual code. Here's Java code that works:
import com.drew.imaging.ImageMetadataReader;
import com.drew.metadata.Directory;
import com.drew.metadata.Metadata;
import com.drew.metadata.Tag;
import com.drew.metadata.file.FileTypeDirectory;
import com.drew.metadata.mp4.Mp4Directory;
import com.drew.metadata.mp4.media.Mp4SoundDirectory;
import com.drew.metadata.mp4.media.Mp4VideoDirectory;
[.....]
Metadata theMetadata = null;
try {
InputStream stream = new URL(theVideoInfo.getLinkToVideo()).openStream();
theMetadata = ImageMetadataReader.readMetadata(stream);
}
} catch (java.lang.Exception exception) {
exception.printStackTrace();
}
Mp4SoundDirectory soundDirectory
= theMetadata.getFirstDirectoryOfType(Mp4SoundDirectory.class);
Mp4VideoDirectory videoDirectory
= theMetadata.getFirstDirectoryOfType(Mp4VideoDirectory.class);
Mp4Directory mp4Directory
= theMetadata.getFirstDirectoryOfType(Mp4Directory.class);
FileTypeDirectory fileTypeDirectory
= theMetadata.getFirstDirectoryOfType(FileTypeDirectory.class);
String numberOfAudioChannels
= soundDirectory.getString(Mp4SoundDirectory.TAG_NUMBER_OF_CHANNELS);
String duration = mp4Directory.getString(Mp4Directory.TAG_DURATION);
String frameRate = videoDirectory.getString(Mp4VideoDirectory.TAG_FRAME_RATE);
String height = videoDirectory.getString(Mp4VideoDirectory.TAG_HEIGHT);
String width = videoDirectory.getString(Mp4VideoDirectory.TAG_WIDTH);
String type = fileTypeDirectory.getString(FileTypeDirectory.TAG_DETECTED_FILE_MIME_TYPE);
I found the constants (TAG_HEIGHT, TAG_WIDTH, etc.) by directly examining the metadata-extractor objects in the debugger. For example, I'd type:
Mp4VideoDirectory.WIDTH
...and the debugger (IntelliJ) would auto-complete the available constants that had the text "WIDTH" in them.

Limiting a Django form's ManyToManyField queryset in a formtools wizard based on selection on previous form

I'm using a SessionWizardView from django-formtools to construct a two-form wizard. The challenge I'm facing is that I need to reference the input from the first form to limit the available querysets on the second form.
To make it more interesting, I'm using crispy forms for layout and the queryset needs to be limited by a method on a related item.
Here's the (much simplified) gist of where I'm at:
Models
class Product(models.Model):
# pk, name, etc....
catalogitem = ForeignKey("myapp.CatalogItem")
colors = ManyToManyField("myapp.Colors")
class Colors(models.Model):
# pk, name, etc....
class CatalogItem(models.Model):
# Colors are stored within CatalogVariants, which I've left
# as a blackbox in this example, since they are retrieved as
# a queryset on this model with this method:
# pk, name, etc....
def get_colors(self):
# Returns a queryset of color objects.
Views
ProductFormWizard(SessionWizardView):
form_list = [
productFormWizard_Step1,
productFormWizard_Step2,
]
def get_context_data(self, **kwargs):
# ...
pass
def get_form_initial(self, step):
initial = {}
# ...
return self.initial_dict.get(step, initial)
def process_step(self, form):
if self.steps.step1 == 1:
pass
return self.get_form_step_data(form)
def done(self, form_list, **kwargs):
return render(self.request, 'done.html', {
'form_data': [form.cleaned_data for form in form_list],
})
Forms
productFormWizard_Step1(forms.ModelForm):
# Defines a form where the user selects a CatalogProduct.
model = Product
productFormWizard_Step2(forms.ModelForm):
"""
Defines a form where the user chooses colors based on
the CatalogProduct they selected in the previous step.
"""
model = Product
Based on research via the Googles and some SO questions (none of which were =directly= related), I'm assuming I need to set the .queryset property on the colors field, but I'm not exactly sure where to do that. Two thoughts:
I would guess it goes in .get_form_initial() somehow, but I'm at a loss as to the best way to achieve that.
Alternatively, the appropriate code might go into the productFormWizard.get_context_data() method somehow.
Within .get_form_initial(), I can do something like this:
if step == '1':
itemID = self.storage.get_step_data('0').data.get('0-pfProduct', "")
if itemID:
obj = CatalogItem.objects.get(id=itemID)
initial['colors'] = obj.get_get_colors()
However, this just selects the available related items... it doesn't limit the list.
Additional Info
Python == 3.5.3
Django == 1.10.6
django-crispy-forms == 1.6.1
django-formtools == 2.0
The solution is to override the .get_form() method on the View:
def get_form(self, step=None, data=None, files=None):
form = super(bzProductFormWizard, self).get_form(step, data, files)
if step == '1':
past_data = self.get_cleaned_data_for_step('0')
product = past_data['product']
form.fields['colors'].queryset = ... #CUSTOM QUERYSET
return form

Passing variables between classes in Tkinter, Python 3

I'm kind of a newbie to Python, and I'm writing some code to take data via a user input and put it into a .csv file. To do that, the program needs to pass data from class to class.
To teach myself how to pass data, I took code from here. I did have to alter the code a bit to get it to start up, making sure that the make_widget and print_it functions can pull the "name" variable stored in self.app_data data structure properly.
from tkinter import *
from tkinter import ttk
class MyApp(Tk):
def __init__(self):
Tk.__init__(self)
self.app_data={'name': StringVar}
container = ttk.Frame(self)
container.pack(side="top", fill="both", expand = True)
self.frames = {}
for F in (PageOne, PageTwo):
frame = F(container, self)
self.frames[F] = frame
frame.grid(row=0, column=0, sticky = NSEW)
self.show_frame(PageOne)
def show_frame(self, cont):
frame = self.frames[cont]
frame.tkraise()
class PageOne(ttk.Frame):
def __init__(self, parent, controller):
ttk.Frame.__init__(self, parent)
self.controller=controller
ttk.Label(self, text='PageOne').grid(padx=(20,20), pady=(20,20))
self.make_widget(controller)
def make_widget(self, controller):
self.controller=controller
self.some_entry = ttk.Entry(self, textvariable=self.controller.app_data['name'], width=8)
self.some_entry.grid()
button1 = ttk.Button(self, text='Next Page',command=lambda: controller.show_frame(PageTwo))
button1.grid()
class PageTwo(ttk.Frame):
def __init__(self, parent, controller):
ttk.Frame.__init__(self, parent)
self.controller=controller
ttk.Label(self, text='PageTwo').grid(padx=(20,20), pady=(20,20))
button1 = ttk.Button(self, text='Previous Page',command=lambda: controller.show_frame(PageOne))
button1.grid()
button2 = ttk.Button(self, text='press to print', command= self.print_it())
button2.grid()
def print_it(self):
value=self.controller.app_data['name'].get()
print ('The value stored in StartPage some_entry = ', value)#What do I put here
#to print the value of some_input from PageOne
When I run this program, it does start up, and I can move from frame to frame, but it does not print the "name" variable.
When I close the window, I get the error:
TypeError: get() missing 1 required positional argument: 'self'
Which the traceback blames on the line:
value=self.controller.app_data['name'].get()
What am I doing wrong? For what it's worth, I'm writing the code in Python 3.5.
I really appreciate any help that you guys could give me.

PySide - QSortFilterProxyModel and QListView - indexWidget pointer get deleted when filtering

I've a problem with a custom QListView I'm trying to make, here the problem:
I'm using QListView to show a list of QWidget by using QListView.setIndexWidget(index,widget).
This is working pretty fine, but now I want to filter the items model by using QSortFilterProxyModel()
with .setFilterWildcard()
It is not working very well because the second time the model is filtered
I got error like this :
RuntimeError: Internal C++ object (PySide.QtGui.QLabel) already deleted.
Without using filtering and QSortFilterProxyModel everything works fine, but it seems I'm missing
something with the filtering operation, the indexWidget() is deleted when using filtering :(
here a sample code where you can reproduce the bug, when list view is shown, hit 1,2 or 3 keyboard
key to activate filtering ( Backspace to set filtering empty to show all items )
Here the sample code to reproduce the problem:
import PySide.QtGui as QtGui
import PySide.QtCore as QtCore
_DEFAULT_ITEM_SIZE = QtCore.QSize(100, 85)
_USER_ROLE = QtGui.QStandardItem.UserType + 1
class CustomItemWidget(QtGui.QWidget):
def __init__(self, parent=None):
super(CustomItemWidget, self).__init__(parent=parent)
#self.setAutoFillBackground(True)
self.main_layout = QtGui.QVBoxLayout(self)
self.label = QtGui.QLabel(self)
self.main_layout.addWidget(self.label)
def paintEvent(self, event):
painter = QtGui.QPainter(self)
painter.setRenderHint(QtGui.QPainter.Antialiasing)
# Default brush and pen
bg_brush = QtGui.QBrush(QtGui.QColor("#8C8C8C"))
pen = QtCore.Qt.NoPen
painter.save()
painter.setPen(pen)
painter.setBrush(bg_brush)
painter.drawRoundedRect(self.rect(), 12, 12)
painter.restore()
def setData(self, role, value):
if role == QtCore.Qt.DisplayRole:
self.label.setText(value)
class CustomItem(QtGui.QStandardItem):
def __init__(self):
super(CustomItem, self).__init__()
self.number = None
self.item_widget = CustomItemWidget()
self.setSelectable(True)
def type(self):
return _USER_ROLE
def data(self, role):
if role == QtCore.Qt.DisplayRole:
value = "DATA %s" % str(self.number)
self.item_widget.setData(role, value)
return value
if role == QtCore.Qt.SizeHintRole:
return _DEFAULT_ITEM_SIZE
return QtGui.QStandardItem.data(self, role)
class CustomItemDelegate(QtGui.QStyledItemDelegate):
def __init__(self, parent=None):
super(CustomItemDelegate, self).__init__(parent=parent)
class CustomItemModel(QtGui.QStandardItemModel):
def __init__(self, parent=None):
super(CustomItemModel, self).__init__(parent)
def flags(self, index):
return QtCore.Qt.ItemIsEnabled | \
QtCore.Qt.ItemIsSelectable | \
QtCore.Qt.ItemIsDragEnabled | \
QtCore.Qt.ItemIsDropEnabled
class CustomItemFilterProxyModel(QtGui.QSortFilterProxyModel):
def __init__(self, parent=None):
super(CustomItemFilterProxyModel, self).__init__(parent)
self.setDynamicSortFilter(True)
self.setFilterCaseSensitivity(QtCore.Qt.CaseInsensitive)
self.setFilterKeyColumn(0)
class CustomView(QtGui.QListView):
def __init__(self, parent=None):
super(CustomView, self).__init__(parent=parent)
self.setIconSize(_DEFAULT_ITEM_SIZE)
self.setMovement(QtGui.QListView.Static)
self.setSelectionMode(QtGui.QAbstractItemView.ExtendedSelection)
self.setSelectionBehavior(QtGui.QAbstractItemView.SelectItems)
self.setViewMode(QtGui.QListView.IconMode)
self.setUniformItemSizes(True)
self.setFlow(QtGui.QListView.LeftToRight)
self.setResizeMode(QtGui.QListView.Adjust)
self.data_model = CustomItemModel(self)
self.proxy_model = CustomItemFilterProxyModel(self)
self.proxy_model.setSourceModel(self.data_model)
self.setModel(self.proxy_model)
def keyPressEvent(self, event):
if event.key() == QtCore.Qt.Key_1:
self.proxy_model.setFilterWildcard("*1*")
print self.proxy_model.filterRegExp()
if event.key() == QtCore.Qt.Key_2:
self.proxy_model.setFilterWildcard("*2*")
print self.proxy_model.filterRegExp()
if event.key() == QtCore.Qt.Key_3:
self.proxy_model.setFilterWildcard("*3*")
print self.proxy_model.filterRegExp()
if event.key() == QtCore.Qt.Key_Backspace:
self.proxy_model.setFilterFixedString("")
print self.proxy_model.filterRegExp()
if event.key() == QtCore.Qt.Key_Plus:
self.addNewItem()
QtGui.QListView.keyPressEvent(self, event)
def addNewItem(self):
item = CustomItem()
item.number = self.data_model.rowCount()
self.addItem(item)
def addItem(self, item):
self.data_model.appendRow(item)
proxy_index = self.proxy_model.mapFromSource(item.index())
self.setIndexWidget(proxy_index, item.item_widget)
if __name__ == '__main__':
import sys
qapplication = QtGui.QApplication(sys.argv)
layout = QtGui.QVBoxLayout()
window = QtGui.QDialog()
window.setLayout(layout)
view = CustomView(window)
view.resize(800, 600)
layout.addWidget(view)
for i in range(0, 10):
item = CustomItem()
item.number = i
view.addItem(item)
window.show()
sys.exit(qapplication.exec_())
or sample code here:
https://gist.github.com/66e29df303d1f1825a53
Can someone please help me on this? is this a known bug ? or I'm doing it completely wrong :P
Thanks in advance for your help.
This is an old question, but as I struggled with a similar problem for quite a while, here the solution I found and a possible explanation:
Instead of caching the custom widget on the model item, I cached the data needed to create the widget. In my case, I wanted to use a custom label with html in order to be able to format parts of text in different colour. Hence, I cached the html string on the item.
Then, in the initStyleOption method of the item delegate, I recreated the widget if it didn't yet exist or had disappeared after filtering:
label = self.parent().indexWidget(modelIndex)
if not label:
label = CustomLabel(item.html)
self.parent().setIndexWidget(modelIndex, label)
The reason why filtering deletes the widget cached on the item is as follows, I believe: the widget can "exist" only in one place. When it is put as indexWidget, it "exists" on a row in the view, not in an item of the model any more. As filtering removes rows from view, widgets on those rows get deleted. - A poor explanation, but I've often got similar surprises when manipulating html elements with JavaScript if I've forgotten to clone the element.