wxPython SetStyle Not Working - python

I'm using wx.TextCtrl.SetStyle() in my code, but it's changing the style of all the text!
Here's my code:
# Get all the words in my TextCtrl
words = self.GetValue().split(" ")
# Find out what the farthest uneditable word is.
farthest_uneditable = (len(words) // length_constants["words_per_block"]) * length_constants["words_per_block"]
# Use this word knowledge to calculate the actual farthest uneditable character is
farthest_position = 0
for word in range(farthest_uneditable):
farthest_position += len(words[word]) + 1
# Make all the uneditable text (everything from the beginning to farthest_uneditable) have a grey background
self.SetStyle(0, farthest_position, wx.TextAttr(wx.NullColour, (84, 84, 84)))
I've tested this code and made sure my farthest_position isn't at the end of my TextCtrl (It's been in the expected position each time). For some reason though, all of the text in my TextCtrl box is getting a grey background.

From the wxPython 2.8 documentation. The last paragraph explains where your problem is:
"
**wxTextCtrl::GetRange
virtual wxString GetRange(long from, long to) const**
Returns the string containing the text starting in the positions from and up to to in the control. The positions must have been returned by another wxTextCtrl method.
Please note that the positions in a multiline wxTextCtrl do not correspond to the indices in the string returned by GetValue because of the different new line representations (CR or CR LF) and so this method should be used to obtain the correct results instead of extracting parts of the entire value. It may also be more efficient, especially if the control contains a lot of data."

Related

Python Curses, reading wide character's attribute from screen

The problem I'm trying to solve is to get a couple ch,att representing the character and the associated attribute currently displayed at some given position.
Now, when the displayed character is not a wide one (i.e. an ASCII character), the method .inch does the job up to masking correctly the results. The issue comes when the displayed character is wide. More precisely I know how to get the given character through .instr, however this function does not return any information about the attribute.
Since, as far as I know, there is no specific function to get the attribute alone, my first attempt was to use .inch, drop the 8 less significant bit and interpret the result as the attribute. This seemed to work to some extent but double checking I realized that reading greek letters (u"u\03b1" for instance) with no attribute in this way returns att = 11.0000.0000 instead of 0. Is there a better way to approach the problem?
EDIT, a minimal example for Python3
import curses
def bin(x):
out = ''
while x > 0:
out = str(x % 2) + out
x = x // 2
return out
def main(s):
s.addstr(1, 1, u'\u03b1')
s.refresh()
chratt = s.inch(1, 1)
att = chratt & 0xFF00
s.addstr(2, 1, bin(att))
s.refresh()
while True:
pass
curses.wrapper(main)
In curses, inch and instr is only for ascii characters as you suspected. "complex" or "wide" characters like characters from utf-8 have another system, as explained here on stackoverflow by one of the ncurses creators.
However, onto the bad news. They aren't implemented in python curses (yet). A pull request was submitted here and it is very close to merging (90%), so if you really need it then why not go contribute yourself?
And if that isn't an option, then you could try to store every change you make to your screen in a variable and then pull the wide characters from there.

wxPython styledtextctrl: How to get number of visible lines with word wrapping enabled?

I am trying to determine the number of lines displayed on the screen in a wxPython styledtextctrl with word wrapping enabled.
I have seen several answers to visible lines here:
wxPython - StyledTextCtrl get currently visible lines
Get visible lines in Scintilla.NET component
The second one if for C# but since the base is still scintilla I thought it was relevant.
The problem with these solutions is while they give the lines, they do so assuming that word wrapping is not enabled. If it is enabled, and some of the lines are wrapped, then the following scintilla function returns the value if wrapping were not enabled:
LinesOnScreen()
So my question is is there any way to get the number of lines on the screen given that word wrapping is enabled?
I assume what you want is the number of document lines, rather than the number of display lines. So if wrapping is enabled, the former will be less than the latter if any lines are wrapped.
As you've already discovered, LinesOnScreen() will give the number of visible display lines. But there is currently no built-in facility to get the number of visible document lines, so it will need to be calculated.
A complete solution could be quite complex, especially if you need to take into account things like line-folding and annotations. But a very basic solution would go something like this:
index = editor.GetFirstVisibleLine()
lines = editor.LinesOnScreen() + index
count = 0
while index < lines:
index += editor.WrapCount(index)
count += 1
But note that this does not attempt to deal with partial lines at the top and bottom of the screen (which is left as an exercise for the reader).

Tkinter simulating the up/down arrow keys

I need to make my own event handler that includes the standard up/down arrow keys' functionalities in a text widget. However, there are some quirks I have to program around somehow.
You might think you could just add/subtract a line and it would go up/down as expected. Well, that's sort of true. But, if you have the word-wrap on or something, you'll go up further than you expected on wrapped lines. Plus, the arrow key might not go up eveningly as it usually does whether or not the word wrap is on (seeing as it will stay on the same character no matter where that character is).
So, how do I simulate the regular functionality in Tkinter?
Here's why I need to reprogram the up arrow key (down also)—this isn't essential to my question, but because people like to ask and come up with alternative approaches, here you go:
I'm making a text editor, and the natural way it does things in a Tkinter Text widget has a bug that I plan to eliminate. The bug is that if you select text with shift-home or something and then try to select further text with shift-up (or shift-down, or control-shift-left or control-shift-right) then it might select or deselect some inappropriate stuff, depending on how you do it. It should be easy enough to fix control-shift-left and control-shift-right, but I have the positioning problems with shift-up and shift-down. I've already reprogrammed home, end, left, right, shift-left and shift-right for similar reasons (and Tkinter is awesome because you actually can reprogram stuff like this).
I'm using Python 3.4.
I'm thinking maybe getting the x,y coordinates of the insert might be helpful. I didn't see anything for that, though (just the mouse coordinates).
I'm not entirely sure what the problem is that you are having. Are you aware you can ask the text widget to calculate an index of the previous line or next line, and to either take wrapped lines into account or ignore the wrapping?
From the tk text widget docs on index modifiers:
+ count ?submodifier? lines
Adjust the index forward by count lines, retaining the same character position within the line. If there are
fewer than count lines after the line containing the current index,
then set the index to refer to the same character position on the last
line of the text. Then, if the line is not long enough to contain a
character at the indicated character position, adjust the character
position to refer to the last character of the line (the newline).
Spaces on either side of count are optional. If the display
submodifier is given, then each visual display line is counted
separately. Otherwise, if any (or no modifier) is given, then each
logical line (no matter how many times it is visually wrapped) counts
just once. If the relevant lines are not wrapped, then these two
methods of counting are equivalent.
You can also use a minus sign to go backwards.
For example:
next_line = the_widget.index("insert +1 display lines")

Python’s `str.format()`, fill characters, and ANSI colors

In Python 2, I’m using str.format() to align a bunch of columns of text I’m printing to a terminal. Basically, it’s a table, but I’m not printing any borders or anything—it’s simply rows of text, aligned into columns.
With no color-fiddling, everything prints as expected.
If I wrap an entire row (i.e., one print statement) with ANSI color codes, everything prints as expected.
However: If I try to make each column a different color within a row, the alignment is thrown off. Technically, the alignment is preserved; it’s the fill characters (spaces) that aren’t printing as desired; in fact, the fill characters seem to be completely removed.
I’ve verified the same issue with both colorama and xtermcolor. The results were the same. Therefore, I’m certain the issue has to do with str.format() not playing well with ANSI escape sequences in the middle of a string.
But I don’t know what to do about it! :( I would really like to know if there’s any kind of workaround for this problem.
Color and alignment are powerful tools for improving readability, and readability is an important part of software usability. It would mean a lot to me if this could be accomplished without manually aligning each column of text.
Little help? ☺
This is a very late answer, left as bread crumbs for anyone who finds this page while struggling to format text with built-in ANSI color codes.
byoungb's comment about making padding decisions on the length of pre-colorized text is exactly right. But if you already have colored text, here's a work-around:
See my ansiwrap module on PyPI. Its primary purpose is providing textwrap for ANSI-colored text, but it also exports ansilen() which tells you "how long would this string be if it didn't contain ANSI control codes?" It's quite useful in making formatting, column-width, and wrapping decisions on pre-colored text. Add width - ansilen(s) spaces to the end or beginning of s to left (or respectively, right) justify s in a column of your desired width. E.g.:
def ansi_ljust(s, width):
needed = width - ansilen(s)
if needed > 0:
return s + ' ' * needed
else:
return s
Also, if you need to split, truncate, or combine colored text at some point, you will find that ANSI's stateful nature makes that a chore. You may find ansi_terminate_lines() helpful; it "patch up" a list of sub-strings so that each has independent, self-standing ANSI codes with equivalent effect as the original string.
The latest versions of ansicolors also contain an equivalent implementation of ansilen().
Python doesn't distinguish between 'normal' characters and ANSI colour codes, which are also characters that the terminal interprets.
In other words, printing '\x1b[92m' to a terminal may change the terminal text colour, Python doesn't see that as anything but a set of 5 characters. If you use print repr(line) instead, python will print the string literal form instead, including using escape codes for non-ASCII printable characters (so the ESC ASCII code, 27, is displayed as \x1b) to see how many have been added.
You'll need to adjust your column alignments manually to allow for those extra characters.
Without your actual code, that's hard for us to help you with though.
Also late to the party. Had this same issue dealing with color and alignment. Here is a function I wrote which adds padding to a string that has characters that are 'invisible' by default, such as escape sequences.
def ljustcolor(text: str, padding: int, char=" ") -> str:
import re
pattern = r'(?:\x1B[#-_]|[\x80-\x9F])[0-?]*[ -/]*[#-~]'
matches = re.findall(pattern, text)
offset = sum(len(match) for match in matches)
return text.ljust(padding + offset,char[0])
The pattern matches all ansi escape sequences, including color codes. We then get the total length of all matches which will serve as our offset when we add it to the padding value in ljust.

Remove last character from wxPython wxTextCtrl?

I am trying to remove the very last character from a TextCtrl object in wxPython. I'm using wxPython 2.8.12 and Python 2.7.2.
My code is as follows:
def btnBkClicked(self, e):
self.txtItem.Remove(self, (self.txtItem.GetLastPosition()[-1]), (self.txtItem.GetLastPosition()))
However, that doesn't work :( What do I need to change/do?
If you need to remove the very last character from the string, try
self.txtItem.SetValue(self.txtItem.GetValue()[:-1])
This code gets current text from TextCtrl and sets and sets its value to this text up to the last symbol.
Regarding your input, TextCtrl.Remove needs two parameters: from and to, which are integer numbers giving the first and the last positions to be removed. As GetLastPosition returns the number of characters in the control, your code should be modified as
self.txtItem.Remove(self.txtItem.GetLastPosition()-1, self.txtItem.GetLastPosition())

Categories