disable arrow keys in Slider - python

When using scikit CollectionViewer (simple image browser) I'd like pressing arrow keys not to trigger going to prev/next image after slider got focus. I used eventFilter for that
from skimage.viewer import ImageViewer
from skimage.viewer.qt import Qt
from skimage.viewer.widgets import Slider
class SilentViewer(ImageViewer): #CollectionViewer with some modifications
def __init__(self, image_collection, update_on='move', **kwargs):
self.image_collection = image_collection
self.index = 0
self.num_images = len(self.image_collection)
first_image = image_collection[0]
super(SilentViewer, self).__init__(first_image)
slider_kws = dict(value=0, low=0, high=self.num_images - 1)
slider_kws['update_on'] = update_on
slider_kws['callback'] = self.update_index
slider_kws['value_type'] = 'int'
self.slider = Slider('frame', **slider_kws)
self.layout.addWidget(self.slider)
self.installEventFilter(self) #Modification to CollectionViewer №1
def update_index(self, name, index):
index = int(round(index))
if index == self.index:
return
index = max(index, 0)
index = min(index, self.num_images - 1)
self.index = index
self.slider.val = index
self.update_image(self.image_collection[index])
def eventFilter(self,obj,evt): #Modification to CollectionViewer №2
try:
print(evt.type(), evt.key(), evt.text())
if (evt.key() == Qt.Key_Left or
evt.key() == Qt.Key_Right or
evt.key() == Qt.Key_Up or
evt.key() == Qt.Key_Down):
print("Ignored arrow button")
return True
else:
return False
except:
print("Smth went wrong")
return False
#for testing reasons
from skimage import data
from skimage.transform import pyramid_gaussian
img = data.coins()
img_pyr = pyramid_gaussian(img, downscale=2, multichannel=False)
img_collection = tuple(img_pyr)
viewer = SilentViewer(img_collection)
viewer.show()
event filter seems to be working: key presses and other events trigger console output. However, arrow keys trigger image change. If I change to update_on='release' (see init), arrow keys do not trigger the image change, but make slider position change.
Could you please tell how can I make the arrow presses to be full consumed by the filter?

Analyzing the Slider source code, it can be seen that it is a container, that is, a widget that has other widgets (QLabel, QSlider and QLineEdit), so the filter must be installed on the internal QSlider and not on the container.
from skimage.viewer import ImageViewer
from skimage.viewer.qt import QtCore
from skimage.viewer.widgets import Slider
class SilentViewer(ImageViewer): # CollectionViewer with some modifications
def __init__(self, image_collection, update_on="move", **kwargs):
self.image_collection = image_collection
self.index = 0
self.num_images = len(self.image_collection)
print(self.num_images)
first_image = image_collection[0]
super(SilentViewer, self).__init__(first_image)
slider_kws = dict(value=0, low=0, high=self.num_images - 1)
slider_kws["update_on"] = update_on
slider_kws["callback"] = self.update_index
slider_kws["value_type"] = "int"
self.slider = Slider("frame", **slider_kws)
self.layout.addWidget(self.slider)
self.slider.slider.installEventFilter(self)
def update_index(self, name, index):
if index == self.index:
return
index = max(index, 0)
index = min(index, self.num_images - 1)
self.index = index
self.update_image(self.image_collection[index])
def eventFilter(self, obj, evt):
if obj is self.slider.slider and evt.type() == QtCore.QEvent.KeyPress:
if evt.key() in (
QtCore.Qt.Key_Left,
QtCore.Qt.Key_Right,
QtCore.Qt.Key_Up,
QtCore.Qt.Key_Down,
):
return True
return super(SilentViewer, self).eventFilter(obj, evt)
def main():
# for testing reasons
from skimage import data
from skimage.transform import pyramid_gaussian
img = data.coins()
img_pyr = pyramid_gaussian(img, downscale=2, multichannel=False)
img_collection = tuple(img_pyr)
viewer = SilentViewer(img_collection)
viewer.show()
if __name__ == "__main__":
main()

Related

VLC causes buffer when embedded in Python Tkinter Frame

I have been working on a music player app that uses VLC to play songs directly from the internet and has features like seeking and a progress bar. But as the normal tkinter progress bar looks kinda old so I used customtkinter.CTkSlider widget but using this causes buffering. The song plays smoothly on using Tk.Scale.
Here, is the code:
# import external libraries
import vlc
import tkinter as Tk
from tkinter import ttk
import pyautogui
import customtkinter
import pafy
# import standard libraries
import os
from threading import Thread, Event
import time
import platform
import requests
class ttkTimer(Thread):
"""a class serving same function as wxTimer... but there may be better ways to do this
"""
def __init__(self, callback, tick):
Thread.__init__(self)
self.callback = callback
self.stopFlag = Event()
self.tick = tick
self.iters = 0
def run(self):
while not self.stopFlag.wait(self.tick):
self.iters += 1
self.callback()
def stop(self):
self.stopFlag.set()
def get(self):
return self.iters
class Player(Tk.Frame):
"""The main window has to deal with events.
"""
def __init__(self, parent, title=None):
Tk.Frame.__init__(self, parent)
self.parent = parent
if title == None:
title = "tk_vlc"
self.parent.title(title)
# Menu Bar
# File Menu
menubar = Tk.Menu(self.parent)
self.parent.config(menu=menubar)
fileMenu = Tk.Menu(menubar)
fileMenu.add_command(label="Open", underline=0, command=self.OnOpen)
fileMenu.add_command(label="Exit", underline=1, command=_quit)
menubar.add_cascade(label="File", menu=fileMenu)
# The second panel holds controls
self.player = None
self.videopanel = ttk.Frame(self.parent)
self.canvas = Tk.Canvas(self.videopanel).pack(fill=Tk.BOTH,expand=1)
self.videopanel.pack(fill=Tk.BOTH,expand=1)
ctrlpanel = ttk.Frame(self.parent)
pause = ttk.Button(ctrlpanel, text="Pause", command=self.OnPause)
play = ttk.Button(ctrlpanel, text="Play", command=self.OnPlay)
stop = ttk.Button(ctrlpanel, text="Stop", command=self.OnStop)
volume = ttk.Button(ctrlpanel, text="Volume", command=self.OnSetVolume)
pause.pack(side=Tk.LEFT)
play.pack(side=Tk.LEFT)
stop.pack(side=Tk.LEFT)
volume.pack(side=Tk.LEFT)
self.volume_var = Tk.IntVar()
self.volslider = Tk.Scale(ctrlpanel, variable=self.volume_var, command=self.volume_sel,
from_=0, to=100, orient=Tk.HORIZONTAL, length=100)
self.volslider.pack(side=Tk.LEFT)
ctrlpanel.pack(side=Tk.BOTTOM)
ctrlpanel2 = ttk.Frame(self.parent)
self.scale_var = Tk.DoubleVar()
self.timeslider_last_val = ""
self.timeslider = customtkinter.CTkSlider(ctrlpanel2, variable=self.scale_var, command=self.scale_sel,
from_=0, to=1000, orient=Tk.HORIZONTAL) # This causes buffer
self.timeslider.pack(side=Tk.BOTTOM, fill=Tk.X,expand=1)
self.timeslider_last_update = time.time()
ctrlpanel2.pack(side=Tk.BOTTOM,fill=Tk.X)
# VLC player controls
self.Instance = vlc.Instance()
self.player = self.Instance.media_player_new()
self.timer = ttkTimer(self.OnTimer, 1.0)
self.timer.start()
self.parent.update()
#self.player.set_hwnd(self.GetHandle()) # for windows, OnOpen does does this
def Extract(self,topic):
"""Will play video on following topic, takes about 10 to 15 seconds to load"""
url = 'https://www.youtube.com/results?q=' + topic
count = 0
cont = ''
try:
cont = requests.get(url)
except:
print('Error','Cannot Connect.. Internet not connected or invalid URL or id.')
cont = ''
data = cont.content
data = str(data)
lst = data.split('"')
for i in lst:
count+=1
if i == 'WEB_PAGE_TYPE_WATCH':
break
if lst[count-5] == "/results":
print("Error","No video found.")
return "https://www.youtube.com"+lst[count-5]
def OnExit(self, evt):
"""Closes the window.
"""
self.Close()
def OnOpen(self):
"""Pop up a new dialow window to choose a file, then play the selected file.
"""
# if a file is already running, then stop it.
self.OnStop()
fullname = pafy.new(self.Extract(pyautogui.password(mask="", title="Enter Song Name:", text="Enter Song Name:"))).getbest().url
self.Media = self.Instance.media_new(fullname)
self.player.set_media(self.Media)
# set the window id where to render VLC's video output
if platform.system() == 'Windows':
self.player.set_hwnd(self.GetHandle())
else:
self.player.set_xwindow(self.GetHandle()) # this line messes up windows
# FIXME: this should be made cross-platform
self.OnPlay()
# set the volume slider to the current volume
#self.volslider.SetValue(self.player.audio_get_volume() / 2)
self.volslider.set(self.player.audio_get_volume())
def OnPlay(self):
"""Toggle the status to Play/Pause.
If no file is loaded, open the dialog window.
"""
# check if there is a file to play, otherwise open a
# Tk.FileDialog to select a file
if not self.player.get_media():
self.OnOpen()
else:
# Try to launch the media, if this fails display an error message
if self.player.play() == -1:
self.errorDialog("Unable to play.")
def GetHandle(self):
return self.videopanel.winfo_id()
#def OnPause(self, evt):
def OnPause(self):
"""Pause the player.
"""
self.player.pause()
def OnStop(self):
"""Stop the player.
"""
self.player.stop()
# reset the time slider
self.timeslider.set(0)
def OnTimer(self):
"""Update the time slider according to the current movie time.
"""
if self.player == None:
return
# since the self.player.get_length can change while playing,
# re-set the timeslider to the correct range.
length = self.player.get_length()
dbl = length * 0.001
self.timeslider.config(to=dbl)
# update the time on the slider
tyme = self.player.get_time()
if tyme == -1:
tyme = 0
dbl = tyme * 0.001
self.timeslider_last_val = ("%.0f" % dbl) + ".0"
# don't want to programatically change slider while user is messing with it.
# wait 2 seconds after user lets go of slider
if time.time() > (self.timeslider_last_update + 2.0):
self.timeslider.set(dbl)
def scale_sel(self, evt):
if self.player == None:
return
nval = self.scale_var.get()
sval = str(nval)
if self.timeslider_last_val != sval:
self.timeslider_last_update = time.time()
mval = "%.0f" % (nval * 1000)
self.player.set_time(int(mval)) # expects milliseconds
def volume_sel(self, evt):
if self.player == None:
return
volume = self.volume_var.get()
if volume > 100:
volume = 100
if self.player.audio_set_volume(volume) == -1:
self.errorDialog("Failed to set volume")
def OnToggleVolume(self, evt):
"""Mute/Unmute according to the audio button.
"""
is_mute = self.player.audio_get_mute()
self.player.audio_set_mute(not is_mute)
# update the volume slider;
# since vlc volume range is in [0, 200],
# and our volume slider has range [0, 100], just divide by 2.
self.volume_var.set(self.player.audio_get_volume())
def OnSetVolume(self):
"""Set the volume according to the volume sider.
"""
volume = self.volume_var.get()
# vlc.MediaPlayer.audio_set_volume returns 0 if success, -1 otherwise
if volume > 100:
volume = 100
if self.player.audio_set_volume(volume) == -1:
self.errorDialog("Failed to set volume")
def errorDialog(self, errormessage):
"""Display a simple error dialog.
"""
Tk.tkMessageBox.showerror(self, 'Error', errormessage)
def Tk_get_root():
if not hasattr(Tk_get_root, "root"): #(1)
Tk_get_root.root= Tk.Tk() #initialization call is inside the function
return Tk_get_root.root
def _quit():
print("_quit: bye")
root = Tk_get_root()
root.quit() # stops mainloop
root.destroy() # this is necessary on Windows to prevent
# Fatal Python Error: PyEval_RestoreThread: NULL tstate
os._exit(1)
if __name__ == "__main__":
# Create a Tk.App(), which handles the windowing system event loop
root = Tk_get_root()
root.protocol("WM_DELETE_WINDOW", _quit)
player = Player(root, title="tkinter vlc")
# show the player window centred and run the application
root.mainloop()
Kindly help
Regards.

PYQT5: Window crashes trying to dynamically create image labels

Apologies if the question is confusing, I'm not the best at wording stuff. I'll try to explain further if required.
Background: I'm creating a simple image viewer that gives the user the ability to cycle through their library of images. I wanted to give the user the ability to browse through their image library (basically a file browser) so I tried creating a little browser that dynamically added QLabels with pixmap for each image in the library.
Problem: First my window crashed without loading any image labels, but since my image library is pretty huge I thought limiting the number would help. The results weren't so different; with the limitation (of only 4 images) the image labels displayed but anytime I try to do anything in the window (resize the window, resize the dock) it crashes. What am I doing wrong here?
Code:
import sys
import os
import time
from PyQt5 import QtWidgets as qtw
from PyQt5 import QtGui as qtg
from PyQt5 import QtCore as qtc
from PIL import ImageQt, Image
import memory_profiler
from DBManager import DBBrowser
class MainWindow(qtw.QMainWindow):
resize_constant = 10
def __init__(self):
self.root = r'Images'
self.control_modifier = False
self.resized = 0
super().__init__()
self.main_widget = qtw.QWidget()
self.main_widget.setLayout(qtw.QVBoxLayout())
self.main_widget.layout().setSpacing(0)
self.setCentralWidget(self.main_widget)
# self.setWindowFlag(qtc.Qt.FramelessWindowHint)
self.create_main_window()
self.viewer()
self.menubar()
self.main_widget.layout().addWidget(self.menu_widget)
self.main_widget.layout().addWidget(self.main_window)
self.main_widget.layout().setContentsMargins(0,0,0,0)
self.show()
def menubar(self):
self.menu_widget = qtw.QWidget()
self.menu_widget.setLayout(qtw.QHBoxLayout())
self.menu_widget.layout().setContentsMargins(0,0,0,0)
self.menu_widget.layout().setSpacing(0)
self.menu_widget.layout().setAlignment(qtc.Qt.AlignLeft)
import_btn = qtw.QPushButton('Import', clicked=self.import_database)
explorer_btn = qtw.QPushButton('Explorer', clicked=self.show_hide_explorer)
import_btn.setFixedWidth(70)
explorer_btn.setFixedWidth(70)
self.menu_widget.layout().addWidget(import_btn)
self.menu_widget.layout().addWidget(explorer_btn)
import_btn.setFocusPolicy(qtc.Qt.ClickFocus)
explorer_btn.setFocusPolicy(qtc.Qt.ClickFocus)
def create_main_window(self):
self.main_window = qtw.QMainWindow()
self.dock = Dock('Explorer')
self.explorer = Explorer()
self.dock.setWidget(self.explorer)
self.dock.setTitleBarWidget(qtw.QWidget())
self.main_window.setFocusPolicy(qtc.Qt.StrongFocus)
self.main_window.setFocus()
self.main_window.addDockWidget(qtc.Qt.LeftDockWidgetArea, self.dock)
def viewer(self):
self.image_handler()
self.image_label = qtw.QLabel(alignment=qtc.Qt.AlignCenter)
self.curr_img_name = self.images[0]
self.curr_img_org = Image.open(os.path.join(self.root, self.curr_img_name))
self.curr_img_qt = ImageQt.ImageQt(self.curr_img_org)
self.curr_img_pixmap = qtg.QPixmap.fromImage(self.curr_img_qt)
self.image_label.setPixmap(self.curr_img_pixmap)
self.image_scrollarea = ImageScrollArea(self)
self.main_window.setCentralWidget(self.image_scrollarea)
self.image_scrollarea.setWidget(self.image_label)
self.image_scrollarea.setWidgetResizable(True)
self.image_scrollarea.setVerticalScrollBarPolicy(qtc.Qt.ScrollBarAlwaysOff)
self.image_scrollarea.setHorizontalScrollBarPolicy(qtc.Qt.ScrollBarAlwaysOff)
self.image_scrollarea.setFocusPolicy(qtc.Qt.NoFocus)
def image_handler(self):
self.images = sorted([(int(image.split(os.extsep)[0]), image.split(os.extsep)[1]) for image in os.listdir(self.root)
if os.path.isfile(os.path.join(self.root, image)) and not image.endswith('.csv')])
self.images = ['.'.join((str(image[0]), image[1])) for image in self.images]
def resize_image(self):
self.curr_img = self.curr_img_org.copy()
self.curr_img_qt = ImageQt.ImageQt(self.curr_img)
self.curr_img_pixmap = qtg.QPixmap.fromImage(self.curr_img_qt)
if self.resized < 0:
self.curr_img.thumbnail((
int(self.curr_img_org.width - (abs(self.resized)/100 * self.curr_img_org.width))
, int(self.curr_img_org.height - (abs(self.resized)/100 * self.curr_img_org.height)))
, Image.BOX)
self.curr_img_qt = ImageQt.ImageQt(self.curr_img)
self.curr_img_pixmap = qtg.QPixmap.fromImage(self.curr_img_qt)
elif self.resized > 0:
self.curr_img = self.curr_img_org.copy()
self.curr_img = self.curr_img.resize((
int((abs(self.resized)/100 * self.curr_img_org.width) + self.curr_img_org.width)
, int((abs(self.resized)/100 * self.curr_img_org.height) + self.curr_img_org.height))
, Image.BOX)
self.curr_img_qt = ImageQt.ImageQt(self.curr_img)
self.curr_img_pixmap = qtg.QPixmap.fromImage(self.curr_img_qt)
def change_image(self, direction):
current_index = self.images.index(self.curr_img_name)
if direction == qtc.Qt.Key_Left:
if current_index == 0:
return
self.curr_img_name = self.images[current_index-1]
self.curr_img_org = Image.open(os.path.join(self.root, self.curr_img_name))
self.curr_img_qt = ImageQt.ImageQt(self.curr_img_org)
self.curr_img_pixmap = qtg.QPixmap.fromImage(self.curr_img_qt)
if self.resized != 0:
self.resize_image()
self.image_label.setPixmap(self.curr_img_pixmap)
self.image_scrollarea.scroll_value = 0
self.image_scrollarea.verticalScrollBar().setValue(self.image_scrollarea.scroll_value)
if direction == qtc.Qt.Key_Right:
if current_index == len(self.images)-1:
return
self.curr_img_name = self.images[current_index+1]
self.curr_img_org = Image.open(os.path.join(self.root, self.curr_img_name))
self.curr_img_qt = ImageQt.ImageQt(self.curr_img_org)
self.curr_img_pixmap = qtg.QPixmap.fromImage(self.curr_img_qt)
if self.resized != 0:
self.resize_image()
self.image_label.setPixmap(self.curr_img_pixmap)
self.image_scrollarea.scroll_value = 0
self.image_scrollarea.verticalScrollBar().setValue(self.image_scrollarea.scroll_value)
def import_database(self):
file_location = qtw.QFileDialog.getOpenFileName(self, 'Import database', os.path.abspath('.'), 'database files (*.db)')
self.explorer.set_database_location(file_location[0])
self.explorer.offset = 0
gallery_data_dict_list = self.explorer.get_items()
self.explorer.set_items(gallery_data_dict_list)
def show_hide_explorer(self):
if self.dock.isVisible():
self.dock.hide()
else:
self.dock.show()
def keyPressEvent(self, event):
if event.key() == qtc.Qt.Key_Left:
self.change_image(qtc.Qt.Key_Left)
if event.key() == qtc.Qt.Key_Right:
self.change_image(qtc.Qt.Key_Right)
if event.key() == qtc.Qt.Key_Up:
self.image_scrollarea.scroll_(120)
if event.key() == qtc.Qt.Key_Down:
self.image_scrollarea.scroll_(-120)
if event.key() == qtc.Qt.Key_Escape:
if self.dock.isVisible():
self.dock.hide()
if event.key() == qtc.Qt.Key_Control:
self.control_modifier = True
if self.control_modifier:
if event.key() == qtc.Qt.Key_E:
self.show_hide_explorer()
def keyReleaseEvent(self, event):
if event.key() == qtc.Qt.Key_Control:
self.control_modifier = False
def wheelEvent(self, event):
if self.control_modifier:
if event.angleDelta().y() == 120:
self.resized += 10
if self.resized >= 90: self.resized = 90
self.resize_image()
self.image_label.setPixmap(self.curr_img_pixmap)
elif event.angleDelta().y() == -120:
self.resized -= 10
if self.resized <= -90: self.resized = -90
self.resize_image()
self.image_label.setPixmap(self.curr_img_pixmap)
class ImageScrollArea(qtw.QScrollArea):
def __init__(self, main_window):
super().__init__()
self.main_window = main_window
self.scroll_value = self.verticalScrollBar().minimum()
self.scroll_constant = 100
def wheelEvent(self, event):
if self.main_window.control_modifier:
event.ignore()
else:
self.scroll_(event.angleDelta().y())
def scroll_(self, y):
if y == -120:
if self.scroll_value + self.scroll_constant >= self.verticalScrollBar().maximum():
self.scroll_value = self.verticalScrollBar().maximum()
else:
self.scroll_value += self.scroll_constant
elif y == 120:
if self.scroll_value - self.scroll_constant <= self.verticalScrollBar().minimum():
self.scroll_value = self.verticalScrollBar().minimum()
else:
self.scroll_value -= self.scroll_constant
self.verticalScrollBar().setValue(self.scroll_value)
class Dock(qtw.QDockWidget):
def __init__(self, title):
super().__init__()
self.setWindowTitle(title)
class Explorer(qtw.QWidget):
def __init__(self):
super().__init__()
# self.database_location = None
self.setLayout(qtw.QVBoxLayout())
self.search_box = qtw.QWidget()
self.search_box.setFixedHeight(int(0.15*self.height()))
self.search_box.setLayout(qtw.QFormLayout())
self.layout().addWidget(self.search_box)
search_edit = qtw.QLineEdit()
search_edit.setPlaceholderText('Enter search term(s)...')
search_bar = qtw.QHBoxLayout()
filter_options = ['tags', 'people', 'date', 'custom...']
filter_option_combobox = qtw.QComboBox()
filter_option_combobox.addItems(filter_options)
filter_option_combobox.setCurrentIndex(0)
filter_option_combobox.currentTextChanged.connect(self.custom_filters)
search_btn = qtw.QPushButton('Search')
search_bar.addWidget(filter_option_combobox)
search_bar.addWidget(search_btn)
self.search_box.layout().addRow(search_edit)
self.search_box.layout().addRow(search_bar)
self.browser()
self.layout().addWidget(self.browser_scrollarea)
def custom_filters(self, value):
if value == 'custom...':
pass
def browser(self):
self.browser_scrollarea = qtw.QScrollArea()
self.browser_scrollarea.setVerticalScrollBarPolicy(qtc.Qt.ScrollBarAlwaysOn)
self.browser_scrollarea.setHorizontalScrollBarPolicy(qtc.Qt.ScrollBarAlwaysOn)
def get_items(self):
self.DBB = DBBrowser()
self.DBB.set_database(database_location=self.database_location)
self.DBB.set_filters()
results = self.DBB.sqlite_select(get='*', order_by='date', order_in='ASC', table='Library')
self.offset += 10
gallery_data_dict_list = [{
'location' : result[0],
'date' : result[1],
'tags' : result[2]
} for result in results]
return gallery_data_dict_list
def set_items(self, gallery_data_dict_list):
self.browser_widget = qtw.QWidget()
self.browser_widget.setLayout(qtw.QGridLayout())
key = 0
for gallery_data_dict in gallery_data_dict_list:
if key > 3:
break
description = qtw.QScrollArea()
text = qtw.QLabel('texttexttexttexttexttexttexttexttexttexttexttexttexttexttexttexttex')
description.setWidget(text)
thumbnail = Image.open(os.path.join(gallery_data_dict['location'], os.listdir(gallery_data_dict['location'])[0]))
thumbnail.thumbnail((250,250))
thumbnail = ImageQt.ImageQt(thumbnail)
self.thumbnail_pixmap = qtg.QPixmap.fromImage(self.thumbnail)
self.thumb = qtw.QLabel(pixmap=self.thumbnail_pixmap)
# text.setFixedWidth(int(self.browser_scrollarea.width()*50/100))
self.browser_widget.layout().addWidget(self.thumb, key, 0)
self.browser_widget.layout().addWidget(description, key, 1)
key += 1
self.browser_scrollarea.setWidget(self.browser_widget)
def set_database_location(self, location):
self.database_location = location
if __name__ == '__main__':
app = qtw.QApplication(sys.argv)
mw = MainWindow()
sys.exit(app.exec_())
Edit:
Following #musicamante's comment, I tried a couple of things to reprex my question and it doesn't seem like anything but a random occurrence with a 99.999% fail rate. I don't say 100% because the same code that crashed a million times worked fine once. I tried stripping the code to nothing but just a QWidget with two widgets; a button to load the browser, and the browser. Nevertheless, it still crashed. I don't know how much more minimal I can go.
Then I realized, I was using PIL.Image and PIL.ImageQt instead of QPixmap. Changing all my images to use qt's pixmap instead fixed the problem, as far as my experience went. I tried using PIL.Image for the main viewer and QPixmap for the browser but it still crashed.
My question now is, is there a way I can make it work well with PIL? Because, unless I'm missing something, I feel like PIL.Image is a lot more feature rich for image manipulation than QPixmap. Namely resizing the image (PIL has a ton of algorithms but QPixmap only has Qt.FastTransformation and Qt.SmoothTransformation as far as I'm aware) and especially the .copy() function. Or alternatively, is there some other library?

CheckableComboBox in PyQGIS (waiting for choice)

I am using this code in QGIS's built-in python-editor.
How can I make the script wait while I mark the necessary lines, and only then continue execution? So far, I cannot do this: the window starts, but the script continues to execute and therefore there is no way to use the select list. For example, can I somehow add the "Ok" button there (as in a standard dialog box)?
from qgis.PyQt import *
from qgis.core import *
class CheckableComboBox(QComboBox):
# Subclass Delegate to increase item height
class Delegate(QStyledItemDelegate):
def sizeHint(self, option, index):
size = super().sizeHint(option, index)
size.setHeight(20)
return size
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Make the combo editable to set a custom text, but readonly
self.setEditable(True)
self.lineEdit().setReadOnly(True)
# Make the lineedit the same color as QPushButton
palette = qApp.palette()
palette.setBrush(QPalette.Base, palette.button())
self.lineEdit().setPalette(palette)
# Use custom delegate
self.setItemDelegate(CheckableComboBox.Delegate())
# Update the text when an item is toggled
self.model().dataChanged.connect(self.updateText)
# Hide and show popup when clicking the line edit
self.lineEdit().installEventFilter(self)
self.closeOnLineEditClick = False
# Prevent popup from closing when clicking on an item
self.view().viewport().installEventFilter(self)
def resizeEvent(self, event):
# Recompute text to elide as needed
self.updateText()
super().resizeEvent(event)
def eventFilter(self, object, event):
if object == self.lineEdit():
if event.type() == QEvent.MouseButtonRelease:
if self.closeOnLineEditClick:
self.hidePopup()
else:
self.showPopup()
return True
return False
if object == self.view().viewport():
if event.type() == QEvent.MouseButtonRelease:
index = self.view().indexAt(event.pos())
item = self.model().item(index.row())
if item.checkState() == Qt.Checked:
item.setCheckState(Qt.Unchecked)
else:
item.setCheckState(Qt.Checked)
return True
return False
def showPopup(self):
super().showPopup()
# When the popup is displayed, a click on the lineedit should close it
self.closeOnLineEditClick = True
def hidePopup(self):
super().hidePopup()
# Used to prevent immediate reopening when clicking on the lineEdit
self.startTimer(100)
# Refresh the display text when closing
self.updateText()
def timerEvent(self, event):
# After timeout, kill timer, and reenable click on line edit
self.killTimer(event.timerId())
self.closeOnLineEditClick = False
def updateText(self):
texts = []
for i in range(self.model().rowCount()):
if self.model().item(i).checkState() == Qt.Checked:
texts.append(self.model().item(i).text())
text = ", ".join(texts)
# Compute elided text (with "...")
metrics = QFontMetrics(self.lineEdit().font())
elidedText = metrics.elidedText(text, Qt.ElideRight, self.lineEdit().width())
self.lineEdit().setText(elidedText)
def addItem(self, text, data=None):
item = QStandardItem()
item.setText(text)
if data is None:
item.setData(text)
else:
item.setData(data)
item.setFlags(Qt.ItemIsEnabled | Qt.ItemIsUserCheckable)
item.setData(Qt.Unchecked, Qt.CheckStateRole)
self.model().appendRow(item)
def addItems(self, texts, datalist=None):
for i, text in enumerate(texts):
try:
data = datalist[i]
except (TypeError, IndexError):
data = None
self.addItem(text, data)
def currentData(self):
# Return the list of selected items data
res = []
for i in range(self.model().rowCount()):
if self.model().item(i).checkState() == Qt.Checked:
res.append(self.model().item(i).data())
return res
print("Starting...")
comunes = ['Ameglia', 'Arcola', 'Bagnone', 'Bolano', 'Carrara', 'Casola', 'Barnaul', 'London']
combo = CheckableComboBox()
combo.resize(350,300)
combo.setWindowTitle('CheckableComboBox')
combo.setWindowFlags(Qt.WindowStaysOnTopHint)
combo.show()
combo.addItems(comunes)
print(combo.currentData())
The problem solved by adding checkable_combobox to the QDialog. Here is the code using QgsCheckableComboBox():
https://gis.stackexchange.com/questions/376901/how-to-put-qgscheckablecombobox-into-standby
from qgis.PyQt import QtGui
from qgis.core import *
planet_list = ["Venus", "Earth", "Mars", "Jupiter", "Pluto"]
items = QgsCheckableComboBox()
items.addItems(planet_list)
dlg = QDialog()
layout = QVBoxLayout()
layout.addWidget(items)
dlg.setLayout(layout)
dlg.exec_()
print('\n\n-----------CheckedItems: ', items.checkedItems())

pyqt update qgraphicsscene with mousewheelevent output

I have a set of images that I want to be able to scroll through. The below code works for me on Mac, such that I can scroll on the visible widget. Printing "count" results in a visible rising or lowering number in the python output. However, I wish to use "count" to update the image and I am kinda stuck on how.
The main question is: How can I use the constantly varying variable "count" from my mousewheel function, and use it to update the image shown in the DCMViewer? I expect some kind of signal to work, but haven't got it working and I'm stuck. Any help is much appreciated.
class DCMViewer(QGraphicsView):
def __init__(self):
super(DCMViewer, self).__init__()
# Create a QGraphicsScene
self.scene = QGraphicsScene()
# Provide image in normalized double format
image = np.double(dcmIm)
image *= 255.0 / image.max()
image = QPixmap.fromImage(qimage2ndarray.array2qimage(image))
image = QGraphicsPixmapItem(image)
# Add the image to the QGraphicsScene window
self.scene.addItem(image)
# Initiate the scene
self.setScene(self.scene)
# Create a mousewheelevent to scroll through images
def wheelEvent(self, QWheelEvent):
super(DCMViewer, self).wheelEvent(QWheelEvent)
global count
wheelcounter = QWheelEvent.angleDelta()
if wheelcounter.y() / 120 == -1:
count += 1
if count >= 21:
count = 21
elif wheelcounter.y() / 120 == 1:
count -= 1
if count <= 0:
count = 0
class Mainwidget(QMainWindow):
def __init__(self):
super(Mainwidget, self).__init__()
# self.initUI()
image_viewer = DCMViewer()
self.setCentralWidget(image_viewer)
self.showFullScreen()
if __name__ == '__main__':
app = QApplication(sys.argv)
win = Mainwidget()
win.showFullScreen()
sys.exit(app.exec_())
To be able to make an item visible, the first task we must do is to identify it, for that we could establish an id for each item. We use the setData() and data() method, then we use the fitInView() method to scale the window to the item, this does not make the item totally visible because if there is another item on it we will get an unwanted output, for that we use setZValue(), the higher the value of Z will be on top of another item, your code will not provide an example that can be tested correctly so create my own example:
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *
class DCMViewer(QGraphicsView):
def __init__(self):
super(DCMViewer, self).__init__()
self.currentId = 0
self.maxId = 0
self.setScene(QGraphicsScene())
directory = QStandardPaths.writableLocation(QStandardPaths.PicturesLocation)
it = QDirIterator(directory, QDir.Files)
while it.hasNext():
pixmap = QPixmap(it.next())
item = QGraphicsPixmapItem(pixmap)
item.setData(0, self.maxId)
self.scene().addItem(item)
self.maxId += 1
def scrollToItem(self, id):
for item in self.scene().items():
if item.data(0) == id:
item.setZValue(1)
self.fitInView(item)
else:
item.setZValue(0)
def wheelEvent(self, event):
wheelcounter = event.angleDelta()
if wheelcounter.y() / 120 == -1:
self.currentId += 1
if self.currentId > self.maxId:
self.currentId = self.maxId -1
elif wheelcounter.y() / 120 == 1:
self.currentId -= 1
if self.currentId <= 0:
self.currentId = 0
self.scrollToItem(self.currentId)
class Mainwidget(QMainWindow):
def __init__(self):
super(Mainwidget, self).__init__()
image_viewer = DCMViewer()
self.setCentralWidget(image_viewer)
self.showFullScreen()
image_viewer.scrollToItem(0)
if __name__ == '__main__':
app = QApplication(sys.argv)
win = Mainwidget()
sys.exit(app.exec_())

How in pyqt change text in QPlainTextEdit() if slider's value is 1, and don't change if the value is 0?

message = QtGui.QPlainTextEdit()
slider = QtGui.QSlider()
def slider_func():
if slider.value() == 0:
'''what to write here ?'''
if slider.value() == 1:
m = str(message.toPlainText())
translated = ''
i = len(m) - 1
while i >= 0:
translated = translated + m[i]
i = i - 1
message.setPlainText(translated)
QtCore.QObject.connect(slider, QtCore.SIGNAL('valueChanged(int)'),
slider_func)
When I set slider to the first tick, the text gets reversed. But when I set it to the begining the text is still reversed. I know that the problem is that once the text was changed it sets the text edit line. Any ideas how to solve it ? Example1, Example2
Here is an example of how to watch the value of the slider, and modify a QPlainText when the value is at certain values.
from PyQt4.QtGui import *
from PyQt4.Qt import *
import sys
class Window(QWidget):
def __init__(self, *args, **kwargs):
super(Window, self).__init__(*args, **kwargs)
layout = QVBoxLayout()
self.setLayout(layout)
self.text = "Please enter some text"
self.edit = QPlainTextEdit(self.text)
layout.addWidget(self.edit)
slider = QSlider(Qt.Horizontal)
layout.addWidget(slider)
slider.valueChanged.connect(self.change_value)
def change_value(self, value):
if value == 0:
self.edit.setPlainText(self.text)
elif value == 1:
self.edit.setPlainText(self.text[::-1])
elif value > 1:
self.edit.setPlainText("Slider value is above 1, value: {0}".format(value))
app = QApplication(sys.argv)
win = Window()
win.show()
sys.exit(app.exec_())
When the QSlider.valueChanged signal is emitted, the new value is carried with it. So in my slot Window.change_value(self, value) the value positional argument will always contain the new QSlider value; which allows you to test against it.

Categories