I'm trying to change the color (Red) of the font that will be highlighted. The problem I'm facing is, as soon as I highlight the first word, all the following text becomes Red.
def cursorPosition(self):
logging.debug("Cursor Positiong changed")
self.cursor = self.ui.captureWindow.textCursor()
if self.cursor.hasSelection():
fmt = QTextCharFormat()
fmt.setForeground(Qt.red)
logging.debug(self.cursor.selectedText())
# We insert the new text, which will override the selected text
self.cursor.insertText(self.cursor.selectedText(), fmt)
# txt = '<p style="color:red;">' + self.cursor.selectedText() + '</p> '
# self.cursor.insertHtml(txt)
# And set the new cursor
self.ui.captureWindow.setTextCursor(self.cursor)
I've tried doing it with two methods, insertText and insertHtml.
insertHtml works if there is a space after the closing tag. Remove that space, it behaves the same way as insertText, everything will be red after the first highlight.
Related
Why is a word being detected as being under the cursor here? The red arrow in the image starts where the cursor actually is. No matter where I place it, as long as it is inside the window, the program thinks a word is being selected. If the cursor is below the text, it defaults to the very last one. If the cursor is above, it defaults to the first one.
IMAGE:
All of my code:
from PyQt5.QtWidgets import QTextEdit, QMainWindow, QApplication
from PyQt5.QtGui import QMouseEvent, QTextCursor
class Editor(QTextEdit):
def __init__(self):
super(Editor, self).__init__()
# make sure this widget is tracking the mouse position at all times
self.setMouseTracking(True)
def mouseMoveEvent(self, mouse_event: QMouseEvent) -> None:
if self.underMouse():
# create a QTextCursor at that position and select text
text_cursor = self.cursorForPosition(mouse_event.pos())
text_cursor.select(QTextCursor.WordUnderCursor)
word_under_cursor = text_cursor.selectedText()
print(word_under_cursor)
# replace substring with placeholder so that repeat occurrences aren't highlighted as well
selected_word_placeholder = self.replace_selected_text_with_placeholder(text_cursor)
word_under_cursor = '<span style="background-color: #FFFF00;font-weight:bold;">' + word_under_cursor + '</span>'
# replace the sentence with the new formatting
self.setHtml(self.toPlainText().replace(selected_word_placeholder, word_under_cursor))
def replace_in_html(self, old_string, new_string):
old_html = self.toHtml()
new_html = old_html.replace(old_string, new_string)
self.setHtml(new_html)
# use placeholder so that repeat occurrences of the word are not highlighted
def replace_selected_text_with_placeholder(self, text_cursor):
# remove the selected word to be replaced by the placeholder
text_cursor.removeSelectedText()
# create a placeholder with as many characters as the original word
word_placeholder = ''
for char in range(10):
word_placeholder += '#'
text_cursor.insertText(word_placeholder)
return word_placeholder
def set_up(main_window):
title_editor = Editor()
title_editor.setText('Venda quente original xiaomi redmi airdots 2 tws fones de ouvido sem fio bluetooth fones controle ai gaming headset come')
main_window.setCentralWidget(title_editor)
main_window.show()
application = QApplication([])
window = QMainWindow()
set_up(window)
application.exec()
The problem is caused by the fact that select() always tries to select something, and even if the mouse is not actually over a text block, it will get the closest word.
The solution is to check if the mouse is actually inside the rectangle of the text block:
if self.underMouse():
pos = mouse_event.pos()
# create a QTextCursor at that position and select text
text_cursor = self.cursorForPosition(pos)
text_cursor.select(QTextCursor.WordUnderCursor)
start = text_cursor.selectionStart()
end = text_cursor.selectionEnd()
length = end - start
block = text_cursor.block()
blockRect = self.document().documentLayout().blockBoundingRect(block)
# translate by the offset caused by the scroll bar
blockRect.translate(0, -self.verticalScrollBar().value())
if not pos in blockRect:
# clear the selection since the mouse is not over the block
text_cursor.setPosition(text_cursor.position())
elif length:
# ensure that the mouse is actually over a word
startFromBlock = start - block.position()
textLine = block.layout().lineForTextPosition(startFromBlock)
endFromBlock = startFromBlock + length
x, _ = textLine.cursorToX(endFromBlock)
if pos.x() > blockRect.x() + x:
# mouse cursor is not over a word, clear the selection
text_cursor.setPosition(text_cursor.position())
Please consider that, as suggested for your previous question, highlighting text using setHtml is not a good choice, as it always resets the contents of the editor; this is not only a problem for performance, but also for usability (even ignoring the scroll bar issue): setHtml always resets the undo stack, so the user cannot use undo operations anymore.
I'm working on a simple markdown parse in tkinter. Concept being that headings can be surrounded by asterisk symbols for example *Heading 1*, **Heading 2**.
I'm use regex to find strings in this format, tag them and change the style of the tags.
The item that I am struggling with is removing the asterisk symbols from the text after they've been searched. I tried some code (included by commented out) but it just removes the tagged text.
My code correctly finds *Heading 1* and turns it in to *Heading 1* but doesn't remove the markdown symbols to get Heading 1
Can anyone help me with an algorithm to remove the asterisk symbols from the headings that retains the formatting?
import tkinter as tk
from tkinter.scrolledtext import ScrolledText
from tkinter import font
class HelpDialog(tk.Toplevel):
"""Seperate window to show the results of SSO Search"""
def __init__(self, parent,text):
super().__init__(parent)
self.title("Help")
self.defaultfont = font.Font(family="Sans Serif",size=12)
self.textbox = ScrolledText(self,height=40,width=80,font=self.defaultfont)
self.textbox.config(wrap=tk.WORD)
self.textbox.grid()
self.textbox.insert(0.0,text)
self.style()
def style(self):
self.h1font = font.Font(family="Sans Serif", size=18, weight="bold")
self.h2font = font.Font(family="Sans Serif", size=14, weight="bold")
self.h3font = font.Font(family="Sans Serif", size=12, weight="bold", slant="italic")
self.textbox.tag_configure("h1",font=self.h1font)
self.textbox.tag_configure("h2",font=self.h2font)
self.textbox.tag_configure("h3",font=self.h3font)
self.tag_match(r"^[\*]{1}[\w\d -]+[\*]{1}$", "h1")
self.tag_match(r"^[\*]{2}[\w\d -]+[\*]{2}$", "h2")
self.tag_match(r"^[\*]{3}[\w\d -]+[\*]{3}$", "h3")
def tag_match(self,regex,tag):
count = tk.IntVar()
self.textbox.mark_set("matchStart", "1.0")
self.textbox.mark_set("matchEnd", "1.0")
while True:
index = self.textbox.search(regex,"matchEnd","end",count=count,regexp=True)
if index=="": break
self.textbox.mark_set("matchStart",index)
self.textbox.mark_set("matchEnd", "%s+%sc" % (index, count.get()))
self.textbox.tag_add(tag,"matchStart","matchEnd")
#Futile attempt to remove the ** from the headings
#text = self.textbox.get("matchStart", "matchEnd")
#orig_length = len(text)
#text = text.replace("*","").ljust(orig_length, " ")
#self.textbox.delete("matchStart", "matchEnd")
#self.textbox.insert("matchStart", text)
if __name__ == '__main__':
text = """*Heading 1*
A paragraph
**Heading 2**
Some more text
***Heading 3***
Conclusion
"""
root = tk.Tk()
root.withdraw()
HelpDialog(root,text)
The short answer is that you can use the delete method of the text widget to delete the characters at the start and end of the range. You can do simplified math on the indexes to adjust them. So, for example, to delete the character at "matchEnd" (which actually represents the spot just after the last character in the matched range) you can do delete("matchEnd-1c") where -1c is short hand for "minus one character".
At the every end of your loop inside of tag_match, add the following two lines:
self.textbox.delete("matchStart")
self.textbox.delete("matchEnd-1c")
However, this code assumes that the markup is just a single byte. You will need to pass information in to tell the function how many characters on each side of the text to delete, since that information doesn't otherwise exist.
For example, you could pass it in like this:
self.tag_match(r"^[\*]{1}[\w\d -]+[\*]{1}$", "h1", 1)
You will then need to adjust the code that deletes the characters to take this information into account. For example, assuming you pass that number in as the variable n, it would look something like this:
def tag_match(self, regex, tag, n):
...
while True:
...
self.textbox.delete("matchEnd-{}c".format(n), "matchEnd")
self.textbox.delete("matchStart", "matchStart+{}c".format(n))
I am working on a Tkinter GUI.
I am wondering how can I remove newline character (\n) at the end of Text widget? It's making my App looking ugly I don't want this.
Example code is:
from tkinter import *
from tkinter import ttk
window = Tk()
window.title('Advanced Calculator v0.9')
window.geometry('500x400')
window.resizable(False, False)
window.configure(background='black')
hist_t = Text(window, background='black', height='7', foreground='red', font='Verdana', highlightcolor='grey', highlightthickness=1, relief='solid')
Label(window, text='History:', background='black', foreground='red').pack(pady=3, anchor='w')
hist_t.pack(fill='x', padx=3)
hist_t.insert('end', '4 + 4 = 8\n2 + 2 = 4\n5 + 3 = 8\n10 + 13 = 23\n30 + 10 = 40\n12 + 8 = 20')
window.mainloop()
Thanks.
Assert:
hist_t = tk.Text(...)
You can always call hist_t.delete('end-1c', 'end') whenever you want to remove the last char, which is newline in this case. You can also remove the last char only if it is newline:
while hist_t.get('end-1c', 'end') == '\n':
hist_t.delete('end-1c', 'end')
Your problem doesn't appear to be any extra newlines, it's simply that you configured it to show 7 lines but you're only inserting 6. If you set the height to 6 you won't see this blank line.
Textbox always inserts a blank line at the end. The problem was compounded when editing textbox and yet another blank line was inserted. However during edit you could remove blank lines so you can't use "end-1c" all the time.
The trick was to remove 1 or more extra blank lines after editing:
# Rebuild lyrics from textbox
self.work_lyrics_score = \
self.lyrics_score_box.get('1.0', tk.END)
while self.work_lyrics_score.endswith('\n\n'):
# Drop last blank line which textbox automatically inserts.
# User may have manually deleted during edit so don't always assume
self.work_lyrics_score = self.work_lyrics_score[:-1]
Note however this is with Linux. For Windows you might need something like:
while self.work_lyrics_score.endswith('\n\r\n\r'):
# Drop last blank line which textbox automatically inserts.
# User may have manually deleted during edit so don't always assume
self.work_lyrics_score = self.work_lyrics_score[:-2]
Or whatever the the control code is for DOS/Windows LF/CR (Line Feed / Carriage Return) typewriter days style is.
I have a textview widget and I can apply tags such as bold, underline etc.
self.tags = {}
self.tags['bold'] = self.textbuffer.create_tag("bold", weight=Pango.Weight.BOLD)
self.tags['italic'] = self.textbuffer.create_tag("italic", style=Pango.Style.ITALIC)
self.tags['underline'] = self.textbuffer.create_tag("underline", underline=Pango.Underline.SINGLE)
self.tags['ubuntu'] = self.textbuffer.create_tag("ubuntu", family = "Ubuntu Mono")
self.tags['size'] = self.textbuffer.create_tag("size", font_desc=Pango.FontDescription.from_string("32"))
The problem is with the last one, If I apply the font size tag, then the rest don't work.
The function that handles the insert-text signal is the following :
def insert_with_tags(self, buffer, start_iter, data, data_len):
if data_len == 1:
start_iter.backward_char()
end = self.textbuffer.props.cursor_position
end_iter = self.textbuffer.get_iter_at_offset(end)
self.textbuffer.apply_tag(self.tags['size'], start_iter, end_iter)
for tag in self.format_toolbar.buttons:
if self.format_toolbar.buttons[tag].get_active():
self.textbuffer.apply_tag(self.tags[tag], start_iter, end_iter)
If I remove the line that applies the font size everything works, but I want to let the user to be able to modify the font (here im testing it with just 1 font, but its the same when I add tags for the rest) while typing and not just modify it for the entire buffer.
I am writing a converter code for our Data Department to convert fixed width files into delmited files. Normally we use import the file into Excel, use the text import wizard to set the field lengths, and then just save as a csv. However we have run into the limitation where we have started getting files that are millions of records long, and thus cant be imported into Excel. The files do not always have spaces in between the fields, espicially so between value fields like phone numbers or zip codes. The headers are also often filled completely in with no spaces.
A sample of a typical fixed width file we are dealing with:
SequenSack and PaFull Name****************************]JOB TITLE****************]HOSP NAME******************************]Delivery Address***********************]Alternate 1 Address********************]Calculated Text**********************************]POSTNET Bar
000001T1 P1 Sample A Sample 123 Any Street Anytown 12345-6789 12345678900
000002T1 P1 Sample A Sample Director of Medicine 123 Any Street Po Box 1234 Anytown 12345-6789 12345678900
The program needs to break file into the following delimited fields:
Sequen
Sack and Pa
Full name
Job Title
Hosp Name
Delivery Address
Alternate Address 1
Calculated Text
POSTNET Bar
Each file as a slightly different width of each field depending on the rest of the job. What i am looking for is a GUI oriented delimiter much like the Excel import wizard for fixed width files. I am writing this tool in Python as a part of a larger tool that does many other file operations such as breaking up files into multiple up, reversing a file, converting from delimited to fixed width and check digit checking. I am using Tkinter for the rest of the tools and it would be ideal if the solution use it as well.
Any help appreciated
If I understand the problem correctly (and there's a good chance I don't...), the simplest solution might be to use a text widget.
Make the first line be a series of spaces the same length as the row. Use a couple of alternating tags (eg: "even" and "odd") to give each character an alternate color so they stand out from one another. The second line would be the header, and any remaining lines would be a couple lines of sample data.
Then, set up bindings on the first row to convert a space into an "x" when the user clicks on a character. If they click on an "x", convert it back to a space. They can then go and click on the character that is the start of each column. When the user is done, you can get the first line of the text widget and it will have an "x" for each column. You then just need a little function that translates that into whatever format you need.
It would look roughly like this (though obviously the colors would be different than what appears on this website)
x x x ...
SequenSack and PaFull Name****************************]JOB...
000001T1 P1 Sample A Sample ...
Here's a quick hack to illustrate the general idea. It's a little sloppy but I think it illustrates the technique. When you run it, click on an area in the first row to set or clear a marker. This will cause the header to be highlighted in alternate colors for each marker.
import sys
import Tkinter as tk
import tkFont
class SampleApp(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
header = "SequenSack and PaFull Name****************************]JOB TITLE****************]HOSP NAME******************************]Delivery Address***********************]Alternate 1 Address********************]Calculated Text**********************************]POSTNET Bar"
sample = "000001T1 P1 Sample A Sample 123 Any Street Anytown 12345-6789 12345678900"
widget = DelimiterWidget(self, header, sample)
hsb = tk.Scrollbar(orient="horizontal", command=widget.xview)
widget.configure(xscrollcommand=hsb.set)
hsb.pack(side="bottom", fill="x")
widget.pack(side="top", fill="x")
class DelimiterWidget(tk.Text):
def __init__(self, parent, header, samplerow):
fixedFont = tkFont.nametofont("TkFixedFont")
tk.Text.__init__(self, parent, wrap="none", height=3, font=fixedFont)
self.configure(cursor="left_ptr")
self.tag_configure("header", background="gray")
self.tag_configure("even", background="#ffffff")
self.tag_configure("header_even", background="bisque")
self.tag_configure("header_odd", background="lightblue")
self.tag_configure("odd", background="#eeeeee")
markers = " "*len(header)
for i in range(len(header)):
tag = "even" if i%2==0 else "odd"
self.insert("end", " ", (tag,))
self.insert("end", "\n")
self.insert("end", header+"\n", "header")
self.insert("end", samplerow, "sample")
self.configure(state="disabled")
self.bind("<1>", self.on_click)
self.bind("<Double-1>", self.on_click)
self.bind("<Triple-1>", self.on_click)
def on_click(self, event):
'''Handle a click on a marker'''
index = self.index("#%s,%s" % (event.x, event.y))
current = self.get(index)
self.configure(state="normal")
self.delete(index)
(line, column) = index.split(".")
tag = "even" if int(column)%2 == 0 else "odd"
char = " " if current == "x" else "x"
self.insert(index, char, tag)
self.configure(state="disabled")
self.highlight_header()
return "break"
def highlight_header(self):
'''Highlight the header based on marker positions'''
self.tag_remove("header_even", 1.0, "end")
self.tag_remove("header_odd", 1.0, "end")
markers = self.get(1.0, "1.0 lineend")
i = 0
start = "2.0"
tag = "header_even"
while True:
try:
i = markers.index("x", i+1)
end = "2.%s" % i
self.tag_add(tag, start, end)
start = self.index(end)
tag = "header_even" if tag == "header_odd" else "header_odd"
except ValueError:
break
if __name__ == "__main__":
app = SampleApp()
app.mainloop()
edit: I now see that you are looking for a gui. I'll leave this incorrect answer for posterity.
import csv
def fixedwidth2csv(fw_name, csv_name, field_info, headings=None):
with open(fw_name, 'r') as fw_in:
with open(csv_name, 'rb') as csv_out: # 'rb' => 'r' for python 3
wtr = csv.writer(csv_out)
if headings:
wtr.writerow(headings)
for line in fw_in:
wtr.writerow(line[pos:pos+width].strip() for pos, width in field_info)