How to create a jupyterhub widget as an object? - python

Using jupyterlab 1.1.4 I want to create an object that displays a widget and that simplifies the definition of the widgets and callback functions. In a first cell I create and start the instance:
from ipywidgets import FileUpload
from ipywidgets import widgets
from IPython.display import display
class MyWidget():
def __init__(self):
self.output = widgets.Output()
self.upload = FileUpload(description='Choose file')
self.upload_filename = None
self.upload.observe(self.upload_file, names=['value'])
display(self.upload, self.output)
#output.capture()
def upload_file(self, args):
self.upload_filename = list(args['new'].keys())[0]
print("File selected: {}".format(self.upload_filename))
w = MyWidget()
When running this cell it shows a FileUpload button, and when I click on it I can choose a file. But when I do so, I expect to see some output (as I print the selected filename). But there is no output.
What am I doing wrong? How to do it correctly?
I even can go farther and change the upload_file method like the following:
#output.capture()
def upload_file(self, args):
a=1/0
self.upload_filename = list(args['new'].keys())[0]
print("File selected: {}".format(self.upload_filename))
which contains an obvious error! But when I run the cell and choose a file, I do not see any error output.
How to fix this?

Related

Prevent QCompleter's popup from hiding itself on connected QLineEdit's text changes

I'm trying to have a QCompleter that's updated with suggestions from a remote api that's being requested on every text change (e.g. like the google search bar).
This is a bit problematic, because the completer hides its popup after text is added to the line edit, and then doesn't show the popup after the model is updated in the slot connected to the reply's finished signal.
The completer not showing after the update can be solved by a call to its complete method, but this causes flicker on the popup because of the hiding before a response is received.
I believe the popup is being hidden here https://code.qt.io/cgit/qt/qtbase.git/tree/src/widgets/util/qcompleter.cpp?h=6.3.1#n1392, but overriding the event filter to show the popup still causes some flicker, and the suggestions disappear until the model is updated in the update_callback.
from PySide6 import QtCore, QtWidgets
from __feature__ import snake_case, true_property # noqa: F401
class Window(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.main_widget = QtWidgets.QWidget(self)
self.set_central_widget(self.main_widget)
self.layout_ = QtWidgets.QVBoxLayout(self.main_widget)
self.line_edit = QtWidgets.QLineEdit(self)
self.layout_.add_widget(self.line_edit)
self.completer_model = QtCore.QStringListModel(self)
self.completer = QtWidgets.QCompleter(self.completer_model, self)
self.line_edit.textEdited.connect(self.update_model)
self.line_edit.set_completer(self.completer)
def update_model(self, query: str):
"""Simulate the network requests that calls self.update_callback when finished."""
QtCore.QTimer.single_shot(0, lambda: self.update_callback(query))
#self.completer_model.set_string_list([query + "completedtext"])
def update_callback(self, query):
self.completer_model.set_string_list([query + "completedtext"])
app = QtWidgets.QApplication()
window = Window()
window.show()
app.exec()
By using setCompleter() the line edit automatically calls setCompletionPrefix() anytime its text is changed. This is done right after the text is updated, so the completer is always updated "too soon" with the previous text, and that can cause flickering.
A possible solution is to always trigger the completer manually, which is achieved by using setWidget() on the completer (instead of setCompleter()) and explicitly calling complete().
class Window(QtWidgets.QMainWindow):
def __init__(self):
# ...
self.completer.setWidget(self.line_edit)
# ...
def update_callback(self, query):
self.completer_model.setStringList([query + "completedtext"])
self.completer.complete()

Problem with button and textbox in jupyter with ipywidgets

I am trying to initialize a variable with the text contained in a textbox when the button is pressed , in a jupyter notebook running python 3. However it seems that I can't access the value in the textbox during the button pressing event and store it for later.
Here is the code :
import ipywidgets as widgets
from IPython.display import display
button = widgets.Button(description="Click Me!")
inp = widgets.Text(description='Path:')
Box = widgets.HBox([button,inp])
def on_button_clicked(b):
return inp.value
path = button.on_click(on_button_clicked)
display(Box
)
Any help would be greatly appreciated.Thanks in advance
The button.on_click function returns None. It doesn't return the value of the function you pass in as an argument. Think of on_click as connecting the button to a function that has side effects. In the example below I use it to append to a list.
import ipywidgets as widgets
from IPython.display import display
button = widgets.Button(description="Click Me!")
inp = widgets.Text(description='text:')
Box = widgets.HBox([button,inp])
value_list = []
def on_button_clicked(b):
value_list.append(inp.value)
print(value_list)
button.on_click(on_button_clicked)
Box
Your value will be available through
inp.value
Please see the documentation

Get ipywidget button within a class to access class parameters through self?

I am writing a class that I want to include multiple widgets that can be displayed in a Jupyter notebook. These widgets should calls class methods that update class parameters. A function that I connect to an ipywidget's events need access to the class instance, I think through self, but I can't figure out how to get this communication to work.
Here's a minimal example:
import numpy as np
import ipywidgets as widgets
class Test(object):
def __init__(self):
self.val = np.random.rand()
display(self._random_button)
_random_button = widgets.Button(
description='randomize self.val'
)
def update_random(self):
self.val = np.random.rand()
print(self.val)
def button_pressed(self):
self.update_random()
_random_button.on_click(button_pressed)
I see how the button_pressed() function sees the Button instance as self, giving "AttributeError: 'Button' object has no attribute 'update_random'".
Is there a way that I can access methods of the class Test through a button that belongs to the class, or is there a better way that I should be structuring this code to ease communication between these components?
The button widget and the on_click should be created (or initialised) in the init method.
The on_click method generates an argument that is sent to the function, but it is not needed in this case so I have just put a *args in the button_pressed function.
The display call is not needed.
When calling a function in a class, you must use self.functionName. That includes the function calls in on_click or observe
In this case, you didn't need the random number generation in the init function.
There are a few examples of Jupyter widgets within classes here: https://github.com/bloomberg/bqplot/tree/master/examples/Applications
import numpy as np
import ipywidgets as widgets
class Test(object):
def __init__(self):
self.random_button = widgets.Button(
description='randomize self.val')
self.random_button.on_click(self.button_pressed)
def update_random(self):
self.val = np.random.rand()
print(self.val)
def button_pressed(self,*args):
self.update_random()
buttonObject = Test()
# display(buttonObject.random_button) # display works but is not required if on the last line in Jupyter cell.
buttonObject.random_button # Widget to be dispalyed - must last last line in cell
When using JupyterLab, if you want the output to show in the notebook cell, rather than in the notebook log, a minor tweak is needed to #DougR's excellent answer:
import numpy as np
import ipywidgets as widgets
# create an output widget
rand_num_output = widgets.Output()
class Test(object):
def __init__(self):
self.random_button = widgets.Button(
description='randomize self.val')
self.random_button.on_click(self.button_pressed)
def update_random(self):
# clear the output on every click of randomize self.val
rand_num_output.clear_output()
# execute function so it gets captured in output widget view
with rand_num_output:
self.val = np.random.rand()
print(self.val)
def button_pressed(self,*args):
self.update_random()
buttonObject = Test()
# display(buttonObject.random_button) # display works but is not required if on the last line in Jupyter cell.
widgets.HBox([buttonObject.random_button, rand_num_output]) # Widget to be dispalyed - must last last line in cell, add output widget

In PyQt, how does one get a shared menu and toolbar to talk to the currently active subwindow?

I have an application which has a main window, which can have multiple subwindows. I would like to have one set of QActions in the main window that interact with the currently selected window. For example, the application might be a text editor, and clicking file->save should save the text file the user is currently working on. Additionally, some QActions are checkable, so their checked state should reflect the state of the currently active window.
Here is a minimum working example that has the basic functionality I want, but I suspect there is a better way to do it (further discussion below the code).
import sys
import PyQt4.QtGui as QtGui
class DisplayWindow(QtGui.QWidget):
def __init__(self, parent=None, name="Main Window"):
# run the initializer of the class inherited from
super(DisplayWindow, self).__init__()
self.myLayout = QtGui.QFormLayout()
self.FooLabel = QtGui.QLabel(self)
self.FooLabel.setText(name)
self.myLayout.addWidget(self.FooLabel)
self.setLayout(self.myLayout)
self.is_foo = False
def toggle_foo(self):
self.is_foo = not self.is_foo
if self.is_foo:
self.FooLabel.setText('foo')
else:
self.FooLabel.setText('bar')
class WindowActionMain(QtGui.QMainWindow):
def __init__(self):
super(WindowActionMain, self).__init__()
self.fooAction = QtGui.QAction('Foo', self)
self.fooAction.triggered.connect(self.set_foo)
self.fooAction.setCheckable(True)
menubar = self.menuBar()
fileMenu = menubar.addMenu('&File')
fileMenu.addAction(self.fooAction)
self.toolbar = self.addToolBar('File')
self.toolbar.addAction(self.fooAction)
self.centralZone = QtGui.QMdiArea()
self.centralZone.subWindowActivated.connect(
self.update_current_window)
self.setCentralWidget(self.centralZone)
self.create_dw("Window 1")
self.create_dw("Window 2")
def create_dw(self, name):
dw = DisplayWindow(name=name)
self.centralZone.addSubWindow(dw)
dw.show()
def update_current_window(self):
""" redirect future actions to affect the newly selected window,
and update checked statuses to reflect state of selected window"""
current_window = self.centralZone.activeSubWindow()
if current_window:
self.current_dw = self.centralZone.activeSubWindow().widget()
self.fooAction.setChecked(self.current_dw.is_foo)
def set_foo(self):
self.current_dw.toggle_foo()
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
ex = WindowActionMain()
ex.show()
sys.exit(app.exec_())
My actual version of DisplayWindow could be useful in many different projects, and I want to package it up so that you don't have to add a lot of code to the main window to use it. Therefore, DisplayWindow, all of its functionality and a list of available actions should be in one module, which would be imported in WindowActionMain's module. I should then be able to add more actions for DisplayWindow without changing any code in WindowActionMain. In particular, I don't want to have to write a little function like WindowActionMain.set_foo(self) just to redirect each action to the right place.
Yes, this is possible by handling the QMenu's aboutToShow signal
and considering the QGuiApplication's focusWindow (or however you get that in Qt4).
Example below shows a generic 'Window' menu acting on the frontmost window.
http://doc.qt.io/qt-4.8/qmenu.html#aboutToShow
http://doc.qt.io/qt-5/qguiapplication.html#focusWindow
def on_windowMenu_aboutToShow(self):
self.windowMenu.clear()
self.newWindowAction = QtWidgets.QAction(self)
self.newWindowAction.setShortcut("Ctrl+n")
self.newWindowAction.triggered.connect(self.on_newWindowAction)
self.newWindowAction.setText("New Window")
self.windowMenu.addAction(self.newWindowAction)
self.windowMenu.addSeparator()
playerWindows = [w for w in self.topLevelWindows() if w.type()==QtCore.Qt.Window and w.isVisible()]
for i, w in enumerate(playerWindows):
def action(i,w):
a = QtWidgets.QAction(self)
a.setText("Show Window {num} - {title}".format(num=i+1, title=w.title()))
a.triggered.connect(lambda : w.requestActivate())
a.triggered.connect(lambda : w.raise_())
self.windowMenu.addAction(a)
action(i,w)
self.windowMenu.addSeparator()
self.closeWindowAction = QtWidgets.QAction(self)
self.closeWindowAction.setShortcut("Ctrl+w")
self.closeWindowAction.triggered.connect(lambda : self.focusWindow().close())
self.closeWindowAction.setText("Close")
self.windowMenu.addAction(self.closeWindowAction)

Paste in the field of QTableView

I need to implement a function in python which handles the "paste" when "ctrl+v" is pressed. I have a QTableView, i need to copy a field of the table and paste it to another field of the table. I have tried the following code, but the problem is that i don't know how to read the copied item (from the clipboard) in the tableView. (As it already copies the field and i can paste it anywhere else like a notepad). Here is part of the code which I have tried:
class Widget(QWidget):
def __init__(self,md,parent=None):
QWidget.__init__(self,parent)
# initially construct the visible table
self.tv=QTableView()
self.tv.show()
# set the shortcut ctrl+v for paste
QShortcut(QKeySequence('Ctrl+v'),self).activated.connect(self._handlePaste)
self.layout = QVBoxLayout(self)
self.layout.addWidget(self.tv)
# paste the value
def _handlePaste(self):
if self.tv.copiedItem.isEmpty():
return
stream = QDataStream(self.tv.copiedItem, QIODevice.ReadOnly)
self.tv.readItemFromStream(stream, self.pasteOffset)
You can obtain the clipboard form the QApplication instance of your app using QApplication.clipboard(), and from the QClipboard object returned you can get the text, image, mime data, etc. Here is an example:
import PyQt4.QtGui as gui
class Widget(gui.QWidget):
def __init__(self,parent=None):
gui.QWidget.__init__(self,parent)
# initially construct the visible table
self.tv=gui.QTableWidget()
self.tv.setRowCount(1)
self.tv.setColumnCount(1)
self.tv.show()
# set the shortcut ctrl+v for paste
gui.QShortcut(gui.QKeySequence('Ctrl+v'),self).activated.connect(self._handlePaste)
self.layout = gui.QVBoxLayout(self)
self.layout.addWidget(self.tv)
# paste the value
def _handlePaste(self):
clipboard_text = gui.QApplication.instance().clipboard().text()
item = gui.QTableWidgetItem()
item.setText(clipboard_text)
self.tv.setItem(0, 0, item)
print clipboard_text
app = gui.QApplication([])
w = Widget()
w.show()
app.exec_()
Note: I've used a QTableWidget cause I don't have a model to use with QTableView but you can adapt the example to your needs.

Categories

Resources