python curses addstr error - but only on my computer - python

I was writing a little program that takes a list and generates a menu out of it in curses (straight up, standard library or whatever, batteries included python's curses) when I noticed the strangest problem (if you'd like, a heavily commented copy of the entire program is below). Simply put, when accepting the results of an os.listdir generated list, curses crashes with an addstr ERR, BUT, if I feed it a hardcoded list, it works fine. This, of course, makes absolutely no sense, right? A list is a list is a list and a list by any other name should still be a list, right?
To make things even more complicated, I sent the code to a friend of mine who works mainly in python2.6 (mine was originally written to work in python3.1). He uncommented the broken_input() call (which feeds the program the os.listdir generated information) and said that it worked fine for him. I have both python 2.6 and 3.1 installed, so I changed my shebang to make the program run in 2.6, and (with the broken_input() uncommented) for me, it still throws the addstr ERR (yet runs fine with the hardcoded input... which is, of course, btw, entirely useless apart from proof of concept).
Thus, my question is this: is there something broken in my python installation (I'm running Ubuntu lucid, with python2.6.5 and 3.1 installed), and, if so, how do I fix it so I can get curses to execute this code properly. And, if it's not my python installation, how can I get the same functionality out of curses (i.e.: paint a menu from a list containing an arbitrary number of items, numbering them so that the user can make a selection based on the item number).
#!/usr/bin/env python3.1
"""curses_mp3eater.py: a curses-based implementation of my mp3eater program;
diplays the contents of cwd, allows user to make a selection. But I'm having
problems getting it to iterate over a list.
v0.1 03.14.11
by skookie sprite
address#gmail.com
"""
import curses, curses.wrapper, os, sys
def working_input():
"""the following is demo code to demonstrate my problem... main will accept the following,
but won't accept the product of a directorylist for reasons that I can't figure out."""
dircontents=['this','is','a','list','','and','it','will','iterate','fine','in','the','(main) function.']
return dircontents
def broken_input():
"""this is the code that I NEED to have work... but for reasons beyond me will not iterate in
the main function. It's a simple list of the contents of the CWD."""
cwd=os.getcwd()
dircontents=[]
for item in os.listdir(cwd):
dircontents += [item]
return dircontents
def main(stdscr):
"""This is the program. Designed to take a list of stuff and display it. If I can solve
that hurdle, I'll add selection mechanisms, and break it across screens - amongst other
things. But, currently, it can only accept the demo code. Uncomment one or the other to
see what I mean."""
#broken_input returns an addstr() ERR, but I don't see the difference between working_input
#and broken_input as they are both just lists.
#working_input() is demo code that illustrates my problem
stuffin=working_input()
#stuffin=broken_input()
#the rest of this stuff works. The problem is with the input. Why?
linenumber=int()
linenumber=6
itemnumber=int()
itemnumber=1
stdscr.clear()
stdscr.border(0)
for item in stuffin:
stdscr.addstr(linenumber, 10, '%s - %s' % (itemnumber, item), curses.A_NORMAL)
linenumber += 1
itemnumber += 1
curses.doupdate()
stdscr.getch()
if __name__ == '__main__':
curses.wrapper(main)

You're stuffing too much onto the screen and thus passing an out-of-bounds line number to addstr. If you make an empty directory to run the program in (or enlarge your terminal window), it works.
To fix this, check the number of lines in the window before the output loop in main.

use screen.scrollok(1) after addstr to allow the text to scroll.

The problem is explained in the addch manual page:
The addch, waddch, mvaddch and mvwaddch routines put the character ch
into the given window at its current window position, which is then
advanced. They are analogous to putchar(3) in stdio(3). If the
advance is at the right margin:
The cursor automatically wraps to the beginning of the next line.
At the bottom of the current scrolling region, and if scrollok is
enabled, the scrolling region is scrolled up one line.
If scrollok is not enabled, writing a character at the lower right
margin succeeds. However, an error is returned because it is not
possible to wrap to a new line
The given program neither catches an error from the lower right margin (probably should say "corner"), nor calls scrollok to allow the data to scroll up. In the latter case, you will lose information which is scrolled up, while handling the exception would allow you to prompt after a screen's worth of data is displayed, and then either quit or display more data.

Related

How to highlight text and paste in place with Python keyboard

I am trying to understand the behavior I am seeing from running my script below and how I can get my desired outcome. Basically I'm using keyboard.add_word_listener() to run a function when the string "test" is typed. It should select the tab trigger key plus the "test" text and then copy it, and then replace it with bbbb and then the copied text.
But if you look at the gif, it doesn't highlight the text. The "bbbb" gets inserted to the left of the "test" text. But it should have replaced the "test" text since it sends ctrl+shift+left which should select the previously entered text. The strange thing is the text still gets copied to the clipboard even though it doesn't look like it got selected. And for some reason it gets pasted in a separate line in the beginning. I don't understand how that is possible. It works on macOS but not Windows.
I also tried replacing ctrl+shift+left,ctrl+shift+left with shift+home but the result is the same.
The script:
import keyboard
import time
def test():
keyboard.send("ctrl+shift+left,ctrl+shift+left")
time.sleep(1)
keyboard.send("ctrl+c")
time.sleep(1)
keyboard.send("b,b,b,b")
time.sleep(1)
keyboard.send("ctrl+v")
keyboard.add_word_listener("test",test,['tab'],False,1)
keyboard.wait()
I've been scratching my head about why this doesn't work for a while now and I've found a way to make it work.
When you call keyboard.send that method parses the given hotkeys, and tries to send a scan_code for each key that you've specified.
It parses the keys via the keyboard.parse_hotkey method. If we call that method ourselves, we can see what codes we get back:
>>> keyboard.parse_hotkey("ctrl+shift+left")
(((29, 57373), (42, 54), (75,)),)
Here we can see that both ctrl and shift has two codes, each for their right/left counterparts.
As you've discovered as well as me, is if you try to call a hotkey like shift+home the button-combination seems to be working correctly, because your position on the document seems to change. However, it seems like the way (at least on my end) the system handles the calls to shift doesn't work when you call just one of the shift codes.
We can change the hotkey to include both of the codes by calling them individually in the hotkey:
>>> keyboard.parse_hotkey("ctrl+right shift+left shift+left")
(((29, 57373), (54,), (42,), (75,)),)
If we update your code, to include the above hotkey instead, we can see that the expected behavior is happening:
import keyboard
def test():
keyboard.send("ctrl+right shift+left shift+left")
keyboard.send("ctrl+c")
keyboard.send("b,b,b,b")
keyboard.send("ctrl+v")
keyboard.add_word_listener("test", test, ['tab'], False, 1)
keyboard.wait()
At the time of writing I don't really know exactly why the above code works while your original code doesn't.

Clearing Print in Python

So I'm pretty new to both coding and this website, so please bear with me if this is stupid:
I'm working on a personal project and would like to find a way to clear "print()" statements in python 3.6. For example:
print("The user would see this text.")
but if I continue
print("The user would see this text.")
print("They would also see this text.")
Is there a way to make it so a user would only see the second print statement?
I have seen "os.system('cls')" and "os.system('clear')" recommended, but I get these errors for each:
os.system('cls')
resulting in
sh: 1: cls: not found
and
os.system('clear')
resulting in
TERM environment variable not set.
Obviously I'm missing something, but if you know what it'd be much appreciated. If you know of another way to do what I'm thinking, that would also be awesome. Thank you for taking the time to read this, and thanks for any help.
Edit: I'm using Repl.it as my IDE. Could this be an issue with that site specifically?
Edit: Downloaded a new IDE to check, and the reply worked. If you are new and using Repl.it, be aware that some code does not function properly.
The method that I've used in the past to 'reprint' something on an existing line is to make use of the standard output directly, coupled with a carriage return to bring the printed statement's cursor back to the start of the line (\r = carriage return), instead of relying on the print function.
In pseudocode:
# Send what you want to print initially to standard output, with a carriage return appended to the front of it.
# Flush the contents of standard output.
# Send the second thing you want to print to standard output.
A working example in Python:
import sys
sys.stdout.write('\rThe user would see this text')
sys.stdout.flush()
sys.stdout.write('\rThe user would also see this text')
Edit
Figured I'd add an example where you can actually see the code working, since the working example above is going to execute so quickly that you'll never see the original line. The below code incorporates a sleep so that you can see it print the first line, wait, then reprint the line using the second string:
import sys
from time import sleep
sys.stdout.write('\rThe user would see this text')
sys.stdout.flush()
sleep(2)
sys.stdout.write('\rThe user would also see this text')

Leave incomplete line on screen when hitting Ctrl-C in ipython 5.0+

In older (I believe pre-5.0) versions of IPython, if I was working on a line/block, and suddenly discovered I needed to investigate something else to finish it, my approach was to hit Ctrl-C, which left the incomplete line/block on screen, but unexecuted, and gave me a fresh prompt. That is, I'd see something like:
In [1]: def foo():
...: stuff^C # <-- Just realized I needed to check something on stuff usage
In [2]: # <-- cursor goes to new line, but old stuff still on terminal
In newer IPython (which seems to have switched from readline to prompt_toolkit as the "CLI support framework"), the behavior of Ctrl-C differs; now, instead of giving me a newline, it just resets the current one, discarding everything I've typed and returning the cursor to the beginning of the line.
# Before:
In [1]: def foo():
...: stuff
# After Ctrl-C:
In [1]: # Hey, where'd everything go?
This is extremely annoying, since I can no longer see or copy/paste the code I was working on to resume my work after I've done whatever side task precipitated the need for a fresh prompt.
My question is: Is there any way to restore the old IPython behavior, where Ctrl-C does the following things:
Does not execute the line/block typed so far
Leaves it on the screen
Ability to choose (at config time is fine) whether to add to the history (this would be personal preference; do you want half-formed stuff in the history, or just on the terminal for copy/paste?)
Provides me with a fresh prompt below the text typed so far
I've searched everywhere, and the most I've found is a bug report comment that mentions this new behavior in passing as "...a change from earlier versions of IPython, but it is intentional."
I haven't been able to find anything documented about modifying the behavior in the IPython or prompt_toolkit documentation; I've found where a lot of these handlers are installed, but attempts at monkey-patching to alter the current behavior have failed (and frankly, monkey-patching undocumented code means I risk it breaking every upgrade, so I'd like to find some semi-supported fix for this; failing that, hacky monkey-patching is acceptable though).
And after more research, I found what appears to be a supported approach, relying on the IPython keyboard shortcuts documentation (documented slightly differently for 5.x and 6.x).
The solution is to create a file in ~/.ipython/profile_default/startup (any name, ending with .py or ipy is fine, e.g. fixctrlc.py), and add the following:
def fix_ctrlc():
'''Set up bindings so IPython 5.0+ responds to Ctrl-C as in pre-5.0
Specific behavior is to have Ctrl-C leave existing typed command on
screen and preserved in history. Easily modified to not put in history.
Since this is run as a startup script, all work, including imports,
is done in this function to avoid polluting global namespace.
Updates made as needed at https://stackoverflow.com/a/45600868/364696
'''
from IPython import get_ipython
from prompt_toolkit.enums import DEFAULT_BUFFER
from prompt_toolkit.keys import Keys
from prompt_toolkit.filters import HasFocus, ViInsertMode, EmacsInsertMode
ip = get_ipython()
# Determine if we're on a version of IPython that needs a fix,
# acquire the key bindings registry from the appropriate location,
# and establish closure state appropriate to that version of IPython
try:
try:
# IPython 5-6; render_as_done doesn't exist, but manual print works
registry = ip.pt_cli.application.key_bindings_registry
redraw_args = {}
doprint = True
except AttributeError:
# IPython 7+ (tested through 8.0.1)
# render_as_done necessary, and removes need for print
registry = ip.pt_app.key_bindings
redraw_args = {'render_as_done': True}
doprint = False
except AttributeError:
# On an old version of IPython that doesn't need the fix, or
# a new version that changed the registry location. Nothing to do.
return
def on_ctrlc(event):
text = event.cli.current_buffer.text.rstrip()
if text:
# Update cursor position to last non-space char in buffer (so Ctrl-C
# with cursor in middle of block doesn't lose text typed after cursor)
event.cli.current_buffer.cursor_position = len(text)
event.cli.current_buffer.text = text
# Redraw so cursor in correct position before print
event.cli._redraw(**redraw_args)
# (Optional) Put non-empty partial commands in history, not just left on screen
# Delete to leave them on screen, but not in history
event.cli.current_buffer.append_to_history()
# Print a newline to move us past currently typed text so it's not
# replaced on redraw
if doprint:
print()
# Reset/redraw prompt
event.cli.reset()
# Clear active buffer, leaving you with fresh, empty prompt
event.cli.current_buffer.reset()
registry.add_binding(
Keys.ControlC,
filter=(HasFocus(DEFAULT_BUFFER) & (ViInsertMode() | EmacsInsertMode()))
)(on_ctrlc)
fix_ctrlc()
del fix_ctrlc # Avoid polluting global namespace
Please feel free to contribute if you find a better solution.

Python IDLE with Python 3.5.2 "crashing"

Okay, so let me just say beforehand: I am new to Python. I was just experimenting with IDLE and then I had this weird "crash". I put "crash" inside speech marks because I'm not sure if it qualifies as a crash, as rather than the program just crashing the way a normal program would in Windows, it still runs, but whenever I press enter and try and get it to accept new text it doesn't do anything. E.g. if you try and type "print('a')" and then hit enter it just goes to the next line (and doesn't print 'a'). I tried to make a simple function which converted an integer to a string where each character in the string was either a '1' or a '0', forming the binary number representing said (unsigned) integer.
>>> def int_to_str(int_in):
str_out=''
bit_val=1<<int_in.bit_length()
while(int_in>0):
if(int_in>bit_val):
str_out+='1'
int_in-=bit_val
else:
str_out+='0'
bit_val>>=1
return str_out
>>> print('a')
print('c')
Basically, it becomes completely unresponsive to my input, and allows me to edit/change "print('a')" even though I shouldn't be able to if it had actually "accepted" my input. Why is this? What have I done wrong/messed up?
Also, I made sure it isn't something else I was previously messing around with by closing the shell and opening it again and only putting in said code for the "int_to_string" function, and I haven't changed any settings or imported any modules before hand or anything like that (in case it matters).
EDIT: I tried reinstalling, and that helped a bit in that I can now do other stuff fine, but the moment I try to use the "str_to_int()" function, it has this same weird behaviour of not accepting/interpreting any more user input.
Your while loop never terminates, you need to re-arrange your logic. Printing variables can be an effective debugging tool - like this:
>>> def int_to_str(int_in):
str_out=''
bit_val=1<<int_in.bit_length()
while(int_in>0):
print(int_in, bit_val)
if(int_in>bit_val):
str_out+='1'
int_in-=bit_val
else:
str_out+='0'
bit_val>>=1
return str_out
If your program seems to be going on too long you can stop it with ctrl-c.

Call a function from main script in an imported file

Before I begin, I know there are many questions that sound a lot like this one, but my question is a little different... So here it is...
As the title may of suggested, I am trying to call a function defined in my main.py script in an imported module. However, this situation is a bit different than that of a circular import situation. I have been doing a lot with pygame recently, and decided that I was gonna make a module that contains classes for buttons, text, sounds, and so on. But I want this file to be generic so it can be used with any game or application I make. Buttons usually have draw functions and stuff like that, so I can easily pass those variables into the functions without problem. The problem comes when I get to the part where I want to check if the button is clicked, and if it is do something. I want to have it set up so that I can pass in a string argument for a command, and use the eval() command on it (python 2.7). However, it throws the error of the function not being defined. I know why this is, but I want to see if there is anything I can do to get around this issue to keep the module as "generic" as possible. Below is a basic set of code to help explain what I want to do.
module1.py
class Button(object):
def __init__(self,x=0,y=0,image=None,command=""):
self.x = x
self.y = y
self.image = image
self.command = command
"""
Image this part filled with draw commands and stuff...
These functions work perfectly fine
"""
#Now here is the issue - local is mouse position
def checkClick(self, local):
#If statments here to determine if mouse over button and
#if mouse is clicked... The part below fails
eval(self.command)
main.py
import module1
import pygame
def quitgame():
pygame.quit()
quit()
local = pygame.mouse.get_pos()
b = module1.Button(command="quitgame")
#At this point lets assume that the mouse is overtop the button and the
#following function in the button will run
b.checkClick(local)
The error, as I said before states that the function I try to call is not defined. I have found a workaround for this, so I don't want answers that tell me how I can change this so it does not take a command as input. I would like however, to make it so I can input a command as an argument. Maybe I am not inputing a command the way I should, but I would like to do it like this, especially because the tkinter module allows you to enter a command as input/a variable. Maybe there is not a way to do this like I wish, but I really want to keep this code as reusable as possible with no changing required between games, and I would rather not have to put this code into my games/applications every time I make them (like I said before the code example I gave was just an example, my actual button code is much larger than what I did above). Like I said before as well, I know that there are many questions that are just like this one, but they have not helped me at all with this issue. The others suggested using scripts that are imported as well which contain addition variables and such, but I would rather not do this. Also, I have a workaround that completely gets rid of the issue, but it is not nearly as neat or easy as this would be.
As always, any help would be appreciated and thanks ahead of time for your answers in case I don't get back to you right away.
I want to have it set up so that I can pass in a string argument for a command, and use the eval() command on it (python 2.7).
No, no, no. Pass it a function:
# In main.py
b = module1.Button(command=quitgame)
# In module1.py
def checkClick(self, local):
...
self.command()
eval is almost never the right tool for any job.
If you don't want to define a function just to pass it as a command parameter, you can use a lambda for short (single-expression) functions:
b = module1.Button(command=lambda: do_whatever(some, arguments))

Categories

Resources