need to dynamically instantiate an object of type gtk.TreeViewColumn through a loop for, as in this example: http://dpaste.com/hold/789277/
the output of the code above is:
tvc_0 = gtk.TreeViewColumn('id', gtk.CellRendererText(), text = 0)
tvc_1 = gtk.TreeViewColumn('Aspecto', gtk.CellRendererText(), text = 1)
tvc_2 = gtk.TreeViewColumn('Impactos', gtk.CellRendererText(), text = 2)
I need to transform the output of the above strings in source code
already tried using getattr, i could not results. could someone help me?
Hugs!
Juliano
On possible solution without using evil exec/eval:
tuple_val = (
('id', gtk.CellRendererText(), 0),
('Aspectos', gtk.CellRendererText(), 1),
('Impactos', gtk.CellRendererText(), 2),
)
def tree_view_factory(id, renderer, text):
return gtk.TreeViewColumn(id, renderer, text=text)
tvc_0, tvc_1, tvc_2 = map(tree_view_factory, tuple_val)
You can use the exec statement to dynamically execute source code from a string. For example:
exec """
tvc_0 = gtk.TreeViewColumn('id', gtk.CellRendererText(), text = 0)
tvc_1 = gtk.TreeViewColumn('Aspecto', gtk.CellRendererText(), text = 1)
tvc_2 = gtk.TreeViewColumn('Impactos', gtk.CellRendererText(), text = 2)
"""
Or in the script you linked:
dic = {'0':'id', '1':'Aspecto', '2':'Impacto'}
for coluna in range(0, len(dic.keys())):
exec "tvc_"+str(coluna)+"=gtk.TreeViewColumn('"+dic[str(coluna)]+"', gtk.CellRendererText(), text="+str(coluna)+")"
But be careful, the execution of arbitrary code strings could lead to serious security problems.
Related
I have the following bits of code that creates a toplevel window and parses a dictionary into a Text widget:
def escrito(**kwargs):
write_window = Toplevel(root)
#write_window.title(kwargs) (problematic code)
writing_box = tk.Text(write_window, font = ("calibri", 20), width = 60, height = 15, wrap=WORD)
writing_box.pack(expand = tk.YES, fill = tk.X)
writing_box.grid(row = 0, column = 0, sticky = 'nswe')
texto = '\n'.join(key + ":\n" + value for key, value in kwargs.items())
writing_box.insert("1.0", texto)
def septic_osteo():
escrito(**infections.Septic_arthritis)
Septic_arthritis = {
'Empirical Treatment':
'Flucloxacillin 2g IV 6-hourly',
'If non-severe penicillin allergy':
'Ceftriaxone IV 2g ONCE daily',
'If severe penicillin allergy OR if known to be colonised with
MRSA':
'Vancomycin infusion IV, Refer to Vancomycin Prescribing
Policy',
'If systemic signs of sepsis': 'Discuss with Consultant
Microbiologist'
}
So when I run the code, the escrito functions parses the dictionary and writes its content onto a text widget contained on a Toplevel window. What I would like to know is how to dynamically rename the Toplevel window with the dicitonary's name. I do know that I can do this:
def septic_osteo():
escrito(**infections.Septic_arthritis)
write_window.title('Septic_arthritis)
but I do have like 100 functions like the one above, so, aside from labour intensive, I am not sure is the more pythonic way, so, is there a way that the window can be renamed with the dictionary name? (i.e. 'Septic_arthritis)
Thanks
If your data is in an object named infections, with attributes such as Septic_arthritis, the most straight-forward solution is to pass the data and the attribute as separate arguments, and then use getattr to get the data for the particular infection.
It would look something like this:
def escrito(data, infection):
write_window = Toplevel(root)
write_window.title(infection)
writing_box = tk.Text(write_window, font = ("calibri", 20), width = 60, height = 15, wrap="word")
writing_box.pack(expand = tk.YES, fill = tk.X)
writing_box.grid(row = 0, column = 0, sticky = 'nswe')
texto = '\n'.join(key + ":\n" + value for key, value in getattr(data, infection).items())
writing_box.insert("1.0", texto)
The important bit about the above code is that it uses getattr(data, infection) to get the data for the given infection.
If you want to create a button to call this function, it might look something like this:
button = tk.Button(..., command=lambda: escrito(infections, "Septic_arthritis"))
This will call the command escrito with two arguments: the object that contains all of the infections, and the key to the specific piece of information you want to display.
Let me start by posting some little helper functions I'll use to formulate my questions:
import textwrap
import sys
from pathlib import Path
from PyQt5.Qsci import QsciScintilla
from PyQt5.Qt import * # noqa
def set_style(sci):
# Set default font
sci.font = QFont()
sci.font.setFamily('Consolas')
sci.font.setFixedPitch(True)
sci.font.setPointSize(8)
sci.font.setBold(True)
sci.setFont(sci.font)
sci.setMarginsFont(sci.font)
sci.setUtf8(True)
# Set paper
sci.setPaper(QColor(39, 40, 34))
# Set margin defaults
fontmetrics = QFontMetrics(sci.font)
sci.setMarginsFont(sci.font)
sci.setMarginWidth(0, fontmetrics.width("000") + 6)
sci.setMarginLineNumbers(0, True)
sci.setMarginsForegroundColor(QColor(128, 128, 128))
sci.setMarginsBackgroundColor(QColor(39, 40, 34))
sci.setMarginType(1, sci.SymbolMargin)
sci.setMarginWidth(1, 12)
# Set indentation defaults
sci.setIndentationsUseTabs(False)
sci.setIndentationWidth(4)
sci.setBackspaceUnindents(True)
sci.setIndentationGuides(True)
sci.setFoldMarginColors(QColor(39, 40, 34), QColor(39, 40, 34))
# Set caret defaults
sci.setCaretForegroundColor(QColor(247, 247, 241))
sci.setCaretWidth(2)
# Set edge defaults
sci.setEdgeColumn(80)
sci.setEdgeColor(QColor(221, 221, 221))
sci.setEdgeMode(sci.EdgeLine)
# Set folding defaults (http://www.scintilla.org/ScintillaDoc.html#Folding)
sci.setFolding(QsciScintilla.CircledFoldStyle)
# Set wrapping
sci.setWrapMode(sci.WrapNone)
# Set selection color defaults
sci.setSelectionBackgroundColor(QColor(61, 61, 52))
sci.resetSelectionForegroundColor()
# Set scrollwidth defaults
sci.SendScintilla(QsciScintilla.SCI_SETSCROLLWIDTHTRACKING, 1)
# Current line visible with special background color
sci.setCaretLineBackgroundColor(QColor(255, 255, 224))
# Set multiselection defaults
sci.SendScintilla(QsciScintilla.SCI_SETMULTIPLESELECTION, True)
sci.SendScintilla(QsciScintilla.SCI_SETMULTIPASTE, 1)
sci.SendScintilla(QsciScintilla.SCI_SETADDITIONALSELECTIONTYPING, True)
def set_state1(sci):
sci.clear_selections()
base = "line{} state1"
view.setText("\n".join([base.format(i) for i in range(10)]))
for i in range(0, 10, 2):
region = (len(base) * i, len(base) * (i + 1) - 1)
if i == 0:
view.set_selection(region)
else:
view.add_selection(region)
def set_state2(sci):
base = "line{} state2"
view.setText("\n".join([base.format(i) for i in range(10)]))
for i in range(1, 10, 2):
region = (len(base) * i, len(base) * (i + 1) - 1)
if i == 1:
view.set_selection(region)
else:
view.add_selection(region)
class Editor(QsciScintilla):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
set_style(self)
def clear_selections(self):
sci = self
sci.SendScintilla(sci.SCI_CLEARSELECTIONS)
def set_selection(self, r):
sci = self
sci.SendScintilla(sci.SCI_SETSELECTION, r[1], r[0])
def add_selection(self, r):
sci = self
sci.SendScintilla(sci.SCI_ADDSELECTION, r[1], r[0])
def sel(self):
sci = self
regions = []
for i in range(sci.SendScintilla(sci.SCI_GETSELECTIONS)):
regions.append(
sci.SendScintilla(sci.SCI_GETSELECTIONNSTART, i),
sci.SendScintilla(sci.SCI_GETSELECTIONNEND, i)
)
return sorted(regions)
I've got a couple of questions actually:
Question 1)
if __name__ == '__main__':
app = QApplication(sys.argv)
view = Editor()
set_state1(view)
view.move(1000, 100)
view.resize(800, 300)
view.show()
app.exec_()
I'll get this (you can see the question in the below snapshot):
Question 2)
if __name__ == '__main__':
app = QApplication(sys.argv)
view = Editor()
set_state1(view)
set_state2(view)
view.move(1000, 100)
view.resize(800, 300)
view.show()
app.exec_()
How can I modify the code so I'll be able to restore state1 when pressing ctrl+z?
Right now when using ctrl+z you won't be able to get state1:
mainly because how setText behaves:
Replaces all of the current text with text. Note that the undo/redo
history is cleared by this function.
I've already tried some of the functions posted in the undo and redo docs but no luck so far.
For instance, one of my attempts has been first selecting all text and then using replaceSelectedText and finally restoring the selections from the previous state manually, the result was ugly (i don't want the editor scrolling messing up when undoing/redoing)... Basically, I'd like to get the same feeling than SublimeText.
Btw, this is a little minimal example but in the real-case I'll be accumulating a bunch of operations without committing to scintilla very often... that's why I'm interested to figure out how to rollback to a previous state when using the undoable setText... Said otherwise, i'd like to avoid using Scintilla functions such as insertAt, replaceSelectedText or similars... as I'm using python string builtin functions to modify the buffer internally.
EDIT:
I'm pretty sure beginUndoAction & endUndoAction won't help me to answer question2 but... what about SCI_ADDUNDOACTION? Although the docs are pretty confusing though... :/
Question 1:
The last selection added is automatically set as the Main selection. To remove it, add line sci.SendScintilla(sci.SCI_SETMAINSELECTION, -1) at the end of the set_state1 function.
Question 2:
The way you described it by storing the selections, using the replaceSelectedText, and then using setCursorPosition / reselecting all selections and setFirstVisibleLine to restore the scroll position is one way to go.
Looking at the C++ source of the setText function:
// Set the given text.
void QsciScintilla::setText(const QString &text)
{
bool ro = ensureRW();
SendScintilla(SCI_SETTEXT, ScintillaBytesConstData(textAsBytes(text)));
SendScintilla(SCI_EMPTYUNDOBUFFER);
setReadOnly(ro);
}
You could try setting the text using sci.SendScintilla(sci.SCI_SETTEXT, b"some text"), which doesn't reset the undo/redo buffer.
I downloaded a MultiListBox widget, and a sorting extension to it that I successfully applied. However, this widget has never worked for me in python 3. The libraries are Tkinter (py2) and tkinter (py3).
So, I've been getting it closer and closer to working, but there's a part that I'm not quite understanding how to get past. The apply() function is deprecated, and, it would seem, not even in py3. So be it.
This is what I don't understand:
We've got apply(function, args [, keywords]) and map(function, iterable...)
The apply() function gets called like:
return apply(map, [None] + result) (result is a list of tuples of strings)
The documentation suggests I take the approach of converting apply(function, *args, **keywords) to function(*args, **keywords). This is all well and good, but I can't figure out what to pass as the new function.
return map(what_am_i, result)
Here are some relevant code snippets. Hope this is all clear, but feel free to ask for more. Thanks.
I've added some more code to provide a context for what's going on here. Perhaps that will generate some insights that will make this code more beautiful?
In this example, the result contains a list of length 3 (columns), of 1000-tuples (rows). Each element of those 1000-tuples is a string containing either a Subject, Sender, or Date.
I've now updated with the complete _sort() function.
Now, I've updated with the other instance of refactoring apply() out. I might as well figure this thing out into this question so everyone else can enjoy.
from tkinter import *
class MultiListbox(Frame):
def __init__(self, master, rowslist):
Frame.__init__(self, master)
self.rowslist = []
self.colmapping = {}
self.origData = None
for (row, colwidth) in rowslist:
frame = Frame(self);
b = Button(frame, ...)
b.bind(...)
listbox = Listbox(frame, ...)
self.rowslist.append(listbox)
listbox.bind(...)
...
...
...
def _scroll(self, *args):
for row in self.rowslist:
row.yview(None, args)
# apply(row.yview, args)
...
def get(self, first, last=None):
result = []
for row in self.rowslist:
result.append(row.get(first,last))
if last:
return map(None, result)
#return apply(map, [None] + result)
return result
...
def _sort(self, e):
# get the listbox to sort by (mapped by the header button)
b=e.widget
col, direction = self.colmapping[b]
# get the entire table data into mem
tableData = self.get(0,END)
if self.origData == None:
import copy
self.origData = copy.deepcopy(tableData)
rowcount = len(tableData)
#remove old sort indicators if it exists
for btn in self.colmapping:
lab = btn.cget('text')
if lab[0]=='<': btn.config(text=lab[4:])
btnLabel = b.cget('text')
#sort data based on direction
if direction==0:
tableData = self.origData
else:
if direction==1: b.config(text='<+> ' + btnLabel)
else: b.config(text='<-> ' + btnLabel)
# sort by col
tableData.sort(key=lambda x: x[col], reverse=direction<0)
#clear widget
self.delete(0,END)
# refill widget
for row in range(rowcount):
self.insert(END, tableData[row])
# toggle direction flag
if direction == 1:
direction = -1
else:
direction = direction + 1
self.colmapping[b] = (col, direction)
# =:> End of complete _sort(self, e) <:= #
...
if __name__ == '__main__':
tk = Tk()
Label(tk, text='SortableMultiListbox').pack()
mlb = MultiListbox(tk, (('Subject', 40), ('Sender', 20), ('Date', 10)))
for i in range(1000):
mlb.insert(END, ('Important Message: %d' % i, 'John Doe', '10/10/%04d' % (1900+i)))
mlb.pack(expand=YES,fill=BOTH)
tk.mainloop()
apply(map, [None] + result)
is equivalent to
map(*[None] + result)
or, perhaps more readably,
map(None, *result)
Note that if you want a transpose, it's probably better to use zip(*result):
>>> # Usually the same behavior:
... result = [[1, 2, 3], [4, 5, 6]]
>>> map(None, *result)
[(1, 4), (2, 5), (3, 6)]
>>> zip(*result)
[(1, 4), (2, 5), (3, 6)]
>>> # But if result has only one iterable in it:
... result = [[1, 2, 3]]
>>> map(None, *result)
[1, 2, 3]
>>> zip(*result)
[(1,), (2,), (3,)]
Here is a snippet
self.list_ctrl = wx.ListCtrl(self, size=(-1,100),
style=wx.LC_ICON|wx.LC_ALIGN_LEFT
)
il = wx.ImageList(16,16,True)
png = wx.ArtProvider.GetBitmap(wx.ART_FILE_OPEN,wx.ART_OTHER, (16,16))
il.Add(png)
self.list_ctrl.AssignImageList(il,wx.IMAGE_LIST_NORMAL)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(self.list_ctrl, 0, wx.ALL|wx.EXPAND, 5)
self.SetSizer(sizer)
self.list_ctrl.InsertImageStringItem(0,"1",0)
self.list_ctrl.InsertImageStringItem(1,"2",0)
My problem is that the icons appear to the top of the text which should not happen because I put wx.LC_ALIGN_LEFT in the style. I would like the icons to appear left of the text.
Another problem is that, I want one element per row. In my code it is almost like one element per column.
Can anyone help me with any of these problems?
Thanks.
Looking at the wxPython demo for the ListCtrl, it looks like they use SetImageList() instead of AssignImageList(). Not sure what the difference is though. I don't see where you're inserting any text though. You'd need to use SetStringItem to put text in the other columns from what I can see.
EDIT: Code from wxPython Demo package, ListCtrl demo:
self.il = wx.ImageList(16, 16)
self.idx1 = self.il.Add(images.Smiles.GetBitmap())
self.sm_up = self.il.Add(images.SmallUpArrow.GetBitmap())
self.sm_dn = self.il.Add(images.SmallDnArrow.GetBitmap())
And then we add data / images to the widget
def PopulateList(self):
if 0:
# for normal, simple columns, you can add them like this:
self.list.InsertColumn(0, "Artist")
self.list.InsertColumn(1, "Title", wx.LIST_FORMAT_RIGHT)
self.list.InsertColumn(2, "Genre")
else:
# but since we want images on the column header we have to do it the hard way:
info = wx.ListItem()
info.m_mask = wx.LIST_MASK_TEXT | wx.LIST_MASK_IMAGE | wx.LIST_MASK_FORMAT
info.m_image = -1
info.m_format = 0
info.m_text = "Artist"
self.list.InsertColumnInfo(0, info)
info.m_format = wx.LIST_FORMAT_RIGHT
info.m_text = "Title"
self.list.InsertColumnInfo(1, info)
info.m_format = 0
info.m_text = "Genre"
self.list.InsertColumnInfo(2, info)
items = musicdata.items()
for key, data in items:
index = self.list.InsertImageStringItem(sys.maxint, data[0], self.idx1)
self.list.SetStringItem(index, 1, data[1])
self.list.SetStringItem(index, 2, data[2])
self.list.SetItemData(index, key)
After following the official tutorial here: tutorial
I'm still having issues adding rows/creating a TreeIter object. Here's what my code looks like:
builder = gtk.Builder()
self.treeview = builder.get_object("treeview")
self.treestore = gtk.TreeStore(str)
self.treeview.set_model(self.treestore)
self.id = gtk.TreeViewColumn('ID')
self.type = gtk.TreeViewColumn("Type")
self.readName = gtk.TreeViewColumn("Filename")
self.set = gtk.TreeViewColumn("Set")
self.treeview.append_column(self.id)
self.treeview.append_column(self.readName)
self.treeview.append_column(self.type)
self.treeview.append_column(self.set)
self.cell = gtk.CellRendererText()
self.cell1 = gtk.CellRendererText()
self.cell2 = gtk.CellRendererText()
self.cell3 = gtk.CellRendererText()
self.id.pack_start(self.cell, True)
self.readName.pack_start(self.cell1, True)
self.type.pack_start(self.cell2, True)
self.set.pack_start(self.cell3, True)
self.id.add_attribute(self.cell, 'text', 0)
self.readName.add_attribute(self.cell1, 'text', 1)
self.type.add_attribute(self.cell2, 'text', 2)
self.set.add_attribute(self.cell3, 'text', 3)
self.treeview.set_reorderable(True)
self.readListVP.add(self.treeview)
iter = self.treestore.get_iter(self.treestore.get_path(iter)) #here's where my problem lies
self.treestore.set_value(None, 0, self.fileCountStr)
self.treestore.set_value(None, 1, "paired-end")
self.treestore.set_value(None, 2, self.file)
self.treestore.set_value(None, 3, self.readSetStr)
I spot a number of general problems with the code as well:
You're creating too many CellRenderer's! Use just one for the whole table.
Don't use the Builder()! It's just stupidly overcomplicating things.
You're not adding columns the most efficent way.
Look into the question I've already asked.