Display ellipsis when Rich Layout scrolls past end of screen/console - python

Is it possible to configure rich.Layout() to indicate when the number of items in the layout has exceeded the display size of the current layout?
I would like to be able to tell programmatically when the code is attempting to display too many items for the current Table/Layout to display in order to display ellipsis or a message such as "...and 200 further items". This would allow the program to alert the user that some items are not being displayed.
There is a size attribute in Layout, but that value appears to be an input to constrain the layout to a fixed size rather than an indicator of the current Layout size.
In my current application, I would rather not constrain the size of the Layout in order to use the full available layout size, whatever that value may be.
#!/usr/bin/env python3
from rich.console import Console
from rich.layout import Layout
from rich.table import Table
from rich.pretty import Pretty
# Make too many lines for the Layout/Table to display on the current screen size
MAX_LINES = 1000
def fill_table():
table = Table()
for li in range(MAX_LINES):
table.add_row(Pretty(li))
return table
console = Console()
layout = Layout(name="root")
layout["root"].update(fill_table())
console.print(layout)

I'm a bit late to the party, but I've just had to solve a similar issue. The trick is to use Layout's .render() function like so:
#!/usr/bin/env python3
from rich.console import Console
from rich.layout import Layout
from rich.table import Table
from rich.pretty import Pretty
from rich import print as pprint
# Make too many lines for the Layout/Table to display on the current screen size
MAX_LINES = 1000
def fill_table(max_height):
rows = [Pretty(li) for li in range(MAX_LINES)]
# Subtract 4 lines - that's how many the table's header and footer takes
n_rows = max_height - 4
if len(rows) > n_rows:
rows = rows[: n_rows - 1] + [f"...and {len(rows) - n_rows + 1} further items"]
table = Table()
for row in rows:
table.add_row(row)
return table
console = Console()
layout = Layout(name="root")
render_map = layout.render(console, console.options)
pprint("Region of the layout:", render_map[layout].region)
table = fill_table(render_map[layout].region.height)
layout["root"].update(table)
console.print(layout)
I didn't figure out how to measure the Table directly, but measuring Layout does the trick. I still needed to manually input the number of rows that the table's header+footer takes, which might fail if there is a multi-line column name.
EDIT: I've posted an answer to a similar problem which also takes into account multi-line rows.

Related

How to limit the rightest Gtk.TreeViewColumn's width inside a Gtk.ScrolledWindow?

I cannot prevent the rightest column of a Gtk.TreeView to expand.
As the real Gtk.TreeView may display a greater number of rows, making it usually somewhat greater than the screen's height, it is embedded in a Gtk.ScrolledWindow. This is required. Without it, attaching an empty grid at the right of the treeview, expanding itself horizontally, would fix the problem. Based on this idea, I've tried a workaround that introduces another difficulty (see below).
I have built a minimal working example from the example from https://python-gtk-3-tutorial.readthedocs.io/en/latest/treeview.html#filtering, without filtering nor buttons; and the columns are 80 px wide at least (this works) and their content is horizontally centered. This last detail makes the horizontal expansion of the rightest column visible. In the original example, it does expand too, but as everything is left aligned, this is not really visible. I'd liked to keep the columns' content centered, without seeing the rightest expanded.
This example is minimal, but contains some helping features: you'll find clickable column titles, that will display some information about the clicked column in the console; a remove button (works fine, remove the selected rows) and a paste button that allows to paste new rows from a selection (e.g. from selected lines from a spreadsheet, but there's nothing to check the data are correct, if you paste something that does not convert to int, it will simply crash).
Workaround
A workaround I've tried consist of gathering both the treeview and a horizontally expanding empty right grid at its right inside a grid that would be put inside the Gtk.ScrolledWindow. It works, but causes other subtle problems: in some situations, the treeview does not get refreshed (it happens after a while), yet nothing prevents the main loop to refresh the view (there's no other processing in the background, for instance). To experiment this workaround: comment and uncomment the lines as described in the code below; run the program via python script.py (if you need to install pygobject in a venv, see here), notice the rightest column does not expand to the right any longer, select the 3 first rows and press "remove", then from a spread sheet, select 3 lines of dummy integers as shown below and then press "paste". Scroll down to the last rows: you'll see most of the time that the 3 pasted lines do not show up, even if it is possible to scroll over the last row. Maybe one of them will show up after some time, then another... (or simply select a row, and they'll show up). Strangely, it happens if one has just removed as many lines as one wants to paste after the removal (3 removed, 3 pasted; or 4 removed, 4 pasted etc.).
Example spreadsheet selection:
Question
So, I'd prefer to avoid the workaround (I'm afraid I may find other situations triggering a bad refreshing of the treeview), that I could not fix itself (for instance, setting self.scrollable_treelist.set_propagate_natural_height(True) proved useless, maybe I'm not using it correctly though?) and only attach the treeview itself directly in the Gtk.ScrolledWindow. How to prevent the rightest column to expand, then?
(I've tried to use a fair amount of setters and properties of the cell renderers, the treeview, the treeview columns, the scrolled window, to no avail. Some of them are still in the code below.)
Any solution using and fixing the workaround above would be accepted though.
In any case, the treeview may be scrolled, and lines may be added and removed from it without any refreshing problem.
Source Code
import gi
try:
gi.require_version('Gtk', '3.0')
except ValueError:
raise
else:
from gi.repository import Gtk, Gdk
# ints to feed the store
data_list = [(i, 2 * i, 3 * i, 4 * i, 5 * i) for i in range(40)]
class AppWindow(Gtk.Window):
def __init__(self):
super().__init__(title="Treeview Columns Size Demo")
self.set_border_width(10)
# Setting up the self.grid in which the elements are to be positioned
self.grid = Gtk.Grid()
self.grid.set_column_homogeneous(True)
self.grid.set_row_homogeneous(True)
self.add(self.grid)
# Creating the ListStore model
self.store = Gtk.ListStore(int, int, int, int, int)
for data_ref in data_list:
self.store.append(list(data_ref))
# creating the treeview and adding the columns
self.treeview = Gtk.TreeView(model=self.store)
rend = Gtk.CellRendererText()
rend.set_alignment(0.5, 0.5)
for i, column_title in enumerate([f'n×{p}' for p in [1, 2, 3, 4, 5]]):
column = Gtk.TreeViewColumn(column_title, rend, text=i)
column.set_min_width(80)
# column.set_max_width(80)
# column.set_fixed_width(80)
# column.set_sizing(Gtk.TreeViewColumnSizing(1))
column.set_alignment(0.5)
column.set_clickable(True)
column.connect('clicked', self.on_column_clicked)
self.treeview.append_column(column)
self.treeview.set_hexpand(False)
self.treeview.get_selection().set_mode(Gtk.SelectionMode.MULTIPLE)
# Put the treeview in a scrolled window
self.scrollable_treelist = Gtk.ScrolledWindow()
self.scrollable_treelist.set_vexpand(True)
self.grid.attach(self.scrollable_treelist, 0, 0, 8, 10)
self.scrollable_treelist.add(self.treeview)
# WORKAROUND
# Alternatively, embed the treeview inside a grid containing an
# empty grid to the right of the treeview
# To try it: comment out the previous line; uncomment next lines
# scrolled_grid = Gtk.Grid()
# empty_grid = Gtk.Grid()
# empty_grid.set_hexpand(True)
# scrolled_grid.attach(self.treeview, 0, 0, 8, 10)
# scrolled_grid.attach_next_to(empty_grid, self.treeview,
# Gtk.PositionType.RIGHT, 1, 1)
# self.scrollable_treelist.add(scrolled_grid)
# self.scrollable_treelist.set_propagate_natural_height(True)
# Buttons
self.remove_button = Gtk.Button(label='Remove')
self.remove_button.connect('clicked', self.on_remove_clicked)
self.paste_button = Gtk.Button(label='Paste')
self.paste_button.connect('clicked', self.on_paste_clicked)
self.grid.attach_next_to(self.remove_button, self.scrollable_treelist,
Gtk.PositionType.TOP, 1, 1)
self.grid.attach_next_to(self.paste_button, self.remove_button,
Gtk.PositionType.RIGHT, 1, 1)
self.set_default_size(800, 500)
self.show_all()
# Clipboard (to insert several rows)
self.clip = Gtk.Clipboard.get(Gdk.SELECTION_PRIMARY)
self.clip2 = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD)
def on_column_clicked(self, col):
print(f'col.get_sizing()={col.get_sizing()}')
print(f'col.get_expand()={col.get_expand()}')
print(f'col.get_width()={col.get_width()}')
print(f'col.get_min_width()={col.get_min_width()}')
print(f'col.get_max_width()={col.get_max_width()}')
print(f'col.get_fixed_width()={col.get_fixed_width()}')
def on_remove_clicked(self, widget):
model, paths = self.treeview.get_selection().get_selected_rows()
refs = []
for path in paths:
refs.append(Gtk.TreeRowReference.new(model, path))
for ref in refs:
path = ref.get_path()
treeiter = model.get_iter(path)
model.remove(treeiter)
# print(f'AFTER REMOVAL, REMAINING ROWS={[str(r[0]) for r in model]}')
def on_paste_clicked(self, widget):
text = self.clip.wait_for_text()
if text is None:
text = self.clip2.wait_for_text()
if text is not None:
lines = text.split('\n') # separate the lines
lines = [tuple(L.split('\t')) for L in lines] # convert to tuples
print(f'PASTE LINES={lines}')
for line in lines:
if len(line) == 5:
line = tuple(int(value) for value in line)
self.store.append(line)
win = AppWindow()
win.connect("destroy", Gtk.main_quit)
win.show_all()
Gtk.main()

Python Tkinter: Buttons for every row in a .csv?

Trying my best at learning Tkinter and failing miserably every step of the way.
I'm trying to write a GUI that creates me either a drop-down list, either a button grid, based on row entries in a .csv file that my script imports.
Thing is, both attempts went up in flames, as I am new to this.
Can anyone link me to a resource/ give me the steps for achieving what I want?
Here is the solution. Next time please paste some code you've written so we know what exactly you've tried.
import tkinter as tk
dataMatrix = [] #this will be 2d list containing csv table elements as strings
with open("filename.csv") as file:
for row in file:
dataMatrix.append(row[:-1].split(";"))
mainWindow = tk.Tk()
######## EITHER THIS
optionMenus = []
strVars = []
for i in range(len(dataMatrix)):
strVars.append(tk.StringVar(mainWindow, dataMatrix[i][0]))
#variable containing currently selected value in i-th row, initialised to first element
optionMenus.append(tk.OptionMenu(mainWindow, strVars[-1], *dataMatrix[i])) #drop-down list for i-th row
optionMenus[-1].grid(row = 0, column = i) #placing i-th OptionMenu in window
######## OR THIS
for r in range(len(dataMatrix)):
for c in range(len(dataMatrix[r])):
b = tk.Button(mainWindow, text = dataMatrix[r][c], width = 10)
#creating button of width 10 characters
b.grid(row = r, column = c)
OptionMenu
Control Variables
Generally infohost.nmt.edu and effbot.org are the best resources for learning tkinter.

display result in UI

I am building a user interface with pyside and Qt. I have done nice calculations that I want to display the results of in the UI.
The results are 1 float each and they are stored in:
self.dist_total_disp
self.time_total_disp
I have tried displaying them with a label like:
self.layout = QtGui.QVBoxLayout()
self.plot_window =QtGui.QVBoxLayout()
self.dist_time_label = QtGui.QLabel()
self.dist_time_label.setText("total distance = self.dist_total_disp \ntotal survey time = self.time_total_disp ")
self.plot_window.addWidget(self.dist_time_label)
----COMPILE UI----
self.setLayout(self.layout)
self.layout.addLayout(self.plot_window)
But the problem here is that the setText requires a string and self.dist_total_disp and self.time_total_disp cannot be called from within the string.
Also I would like to display the result on the lower right side of the VBox but I don't want to change QVBoxLayout() to QHBoxLayout().
I feel there should be a QtGui tool that is more suitable for this but I could't find one in the documentation.
Edit:
Note that the calculations are done with input from the UI
This should work:
self.dist_time_label.setText("total distance = {0} \ntotal survey time = {1} ".format(self.dist_total_disp, self.time_total_disp))
For adding the label at the lower side of the VBox you should add a spacer above it.
You need to format the string with the values you want to display
self.dist_time_label.setText("total distance = %f\ntotal survey time = %f" % (self.dist_total_disp, self.time_total_disp))
For displaying the label on the lower right side you would use the alignment paramter of addWidget(widget, stretch=0, alignment=0)
self.plot_window.addWidget(self.dist_time_label, alignment=QtCore.Qt.AlignRight|QtCore.Qt.AlignBottom)

Trialhandler and time measuring in psychopy

For a go-NoGo Task I want to organize pictures with the data.TrialHandler class from psychopy:
trials = data.TrialHandler(ImageList, nReps=1, method='random')
Now I want to code a loop in which psychopy is going into the dictionary, is presenting the first set of pictures (e.g. A_n) and afterwards is going to the second set until the sixth set. I tried the following:
import glob, os, random, sys, time
import numpy.random as rnd
from psychopy import visual, core, event, monitors, gui, logging, data
im_a = glob.glob('./a*') # upload pictures of the a-type, gives out a List of .jpg-files
im_n = glob.glob('./n*') # n-type
im_e = glob.glob('./e*') # e-type
# combining Lists of Pictures
A_n = im_a + im_n
N_a = im_n + im_a
A_e = im_a + im_e
E_a = im_e + im_a
E_n = im_e + im_n
N_e = im_n + im_e
# making a Dictionary of Pictures and Conditions
PicList = [A_n, N_a, A_e, E_a, E_n, N_e] # just the six Combinations
CondList = [im_a,im_n,im_a,im_e,im_e,im_n] # images that are in the GO-Condition
ImageList = []
for imagelist, condition in zip(PicList, CondList):
ImageList.append({'imagelist':imagelist,'condition':condition}) # to associate the picturelist with the GO Conditionlist
for the header I ask an extra question: Combining and associating multiple dictionaries
# Set Experiment
win = visual.Window(color='white',units='pix', fullscr=False)
fixCross=visual.TextStim(win,text='+',color='black',pos=(0.0,0.0), height=40)
corrFb = visual.TextStim(win,text='O',height=40,color='green',pos=[0,0])
incorrFb = visual.TextStim(win,text='X',height=40, color='red',pos=[0,0])
# Start Experiement
trials = data.TrialHandler(ImageList, nReps=1, method='random')
rt_clock = core.Clock()
bitmap = visual.ImageStim(win)
for liste in ImageList[0:5]: # to loop through all 6 conditions
keys = []
for i,Pictures in enumerate(liste): # to loop through all pictures in each condition
bitmap.setImage(Pictures) # attribute error occurs, not if I use Pictures[0][0], even though in this case every pictures is the same
bitmap.draw()
win.flip()
rt_clock.reset()
resp = False
while rt_clock.getTime() < 2.0: # timelimit is defined 2 s
if not resp:
resp = event.getKeys(keyList=['space'])
rt = rt_clock.getTime()
if bool(resp) is (Pictures in CondList): # at this point python should have access to the Dictionary in which the associated GO Pictures are saved
corrFb.draw()
accu=1 # doesn't work yet
else:
incorrFb.draw()
accu=0
win.flip()
core.wait(0.5)
trials.addData('rt_'+str(i), rt) # is working well when loop: if trial in trials: ...; in this case trialHAndler is not used, therefor trials.addData is not working
trials.addData('accu_'+str(i), accu)
trials.saveAsExcel(datanames)
core.quit()
There are a few problems in this code: first it only presents one pictuere for six times, but not six different pictures [1]
and secondly a totally different problem [2] ist the time measuring and the saving of the accuracy which the trialhandler is doing, but for each trial. So it adds up all the RT's for each trial. I want to get the RT's for each image. I tried a few things like an extra stimulus.trialhandler for the stimuli and an extraloop in the end which gives me the last RT but not each. --> is answered below!!!
for stimuli in stimulus: stimulus.addData('rt', rt)
I know these four questions are a lot for one question, but maybe somebody can give me some good ideas of how I can solve these... Thanks everybody!
The reason for your problem labelled [1] is that you set the image to PicList[0][0] which never changes. As Mike is suggesting above you need::
for i,thisPic in enumerate(PicList):
bitmap.setImage(thisPic) #not PicList[0][0]
But maybe you need to go back to basics so that you actually use the trial handler to handle your trials ;-)
Create a single list of dictionaries where one dictionary represents one trial, and then run through those in order (tell the TrialHandler to use the list 'sequential' rather than 'random'). So the loops that you're currently using should just be to create your list of condition dicts, not to run the trials. Then pass that one list to the trial handler::
trials = TrialHandler(trialTypes = myCondsListInOrder, nReps=1, method='sequential')
for thisTrial in trials:
pic = thisTrial['pic']
stim.setImage(pic)
...
trials.addData('rt', rt)
trials.addData('acc',acc)
Also, I would output your data not using the excel format, but the 'long wide' format::
trials.saveAsWideText('mydataFile.csv')
best wishes,
Jon
(A) This isn't relevant to your question but will improve performance.
The line:
bitmap = visual.ImageStim(win)
Shouldn't occur within the loop. i.e. you should initialise each stimulus only once, then within a loop you just update that the properties of that stimulus, e.g. with bitmap.setImage(…). So shift this initialisation line to the top, where you create the TextStims.
(B) [deleted: I hadn't paid attention to the first code block.]
(C)
bitmap.draw(pictures)
This line doesn't take any arguments. It should just be bitmap.draw(). And anyway, it isn't clear what 'pictures' refers to. Remember that Python is case sensitive. This isn't the same thing as 'Pictures' defined in the loop above. I'm guessing that you want to update what picture is being shown? In that case, then you need to be doing the bitmap.setImage(…) line within this loop, not above, where you will always be drawing a fixed picture as that is the only one that gets set on each trial.
(D) Re the RTs, you are saving this only once per trial (check the indentation). If you want to save one per image, you need to indent these lines again. Also, you only get one line per trial in the data output. If you want to record multiple RTs per trial, you will need to give them unique names, e.g. rt_1, rt_2, …, rt_6 so they each appear in a separate column. e.g. you could use an enumerator for this loop:
for i, picture in enumerate(Piclist)
# lots of code
# then save data:
trials.addData('rt_'+str(i), rt)

How to remove widgets from grid in tkinter?

I have a grid in a tkinter frame which displays query results. It has a date field that is changed manually and then date is used as a parameter on the query for those results. every time the date is changed, obviously the results change, giving a different amount of rows. The problem is that if you get less rows the second time you do it, you will still have results from the first query underneath those and will be very confusing.
My question is, how can i remove all rows from the frame that have a row number greater than 6 (regardless of what's in it)?
By the way, I'm running Python 3.3.3. Thanks in advance!
Calling the method grid_forget on the widget will remove it from the window -
this example uses the call grid_slaves on the parent to findout all
widgets mapped to the grid, and then the grid_info call to learn about
each widget's position:
>>> import tkinter
# create:
>>> a = tkinter.Tk()
>>> for i in range(10):
... label = tkinter.Label(a, text=str(i))
... label.grid(column=0, row=i)
# remove from screen:
>>> for label in a.grid_slaves():
... if int(label.grid_info()["row"]) > 6:
... label.grid_forget()

Categories