I have a program written in Python 3 and using Drag and Drop. I want to make Video tutorials. But when the Video capture is working, Drop fails. The capture program seems to intercept some signal.
I have tried several programs (Jing and others) with the same result. I am testing with a very simple Python script. When the capture is working, the lines which normally indicate the drop location do not appear. The get_data and the received_data functions are not launched.
I am in Windows 8. I have not tested in Linux.
Below, my python3 test code.
#!/usr/bin/env python
# example treeviewdnd.py modified
from gi.repository import Gtk, Gdk, Pango, GObject
class TreeViewDnDExample:
TARGETS = [
('MY_TREE_MODEL_ROW', Gtk.TargetFlags.SAME_WIDGET, 0),
('text/plain', 0, 1),
('TEXT', 0, 2),
('STRING', 0, 3),
]
# close the window and quit
def delete_event(self, widget, event, data=None):
Gtk.main_quit()
return False
def clear_selected(self, button):
selection = self.treeview.get_selection()
model, iter = selection.get_selected()
if iter:
model.remove(iter)
return
def __init__(self):
# Create a new window
self.window = Gtk.Window()
self.window.set_title("URL Cache")
self.window.set_size_request(200, 200)
self.window.connect("delete_event", self.delete_event)
self.scrolledwindow = Gtk.ScrolledWindow()
self.vbox = Gtk.VBox()
self.hbox = Gtk.HButtonBox()
self.vbox.pack_start(self.scrolledwindow, True, True, 0)
self.vbox.pack_start(self.hbox, False, True, 0)
self.b0 = Gtk.Button('Clear All')
self.b1 = Gtk.Button('Clear Selected')
self.hbox.pack_start(self.b0, True, True, 0)
self.hbox.pack_start(self.b1, True, True, 0)
# create a liststore with one string column to use as the model
self.liststore = Gtk.ListStore(str)
for a in "abcd" :
self.liststore.append([a])
# create the TreeView using liststore
self.treeview = Gtk.TreeView(self.liststore)
# create a CellRenderer to render the data
self.cell = Gtk.CellRendererText()
# create the TreeViewColumns to display the data
self.tvcolumn = Gtk.TreeViewColumn('URL', self.cell, text=0)
# add columns to treeview
self.treeview.append_column(self.tvcolumn)
self.b0.connect_object('clicked', Gtk.ListStore.clear, self.liststore)
self.b1.connect('clicked', self.clear_selected)
# make treeview searchable
self.treeview.set_search_column(0)
# Allow sorting on the column
self.tvcolumn.set_sort_column_id(0)
# Allow enable drag and drop of rows including row move
self.treeview.enable_model_drag_source( Gdk.ModifierType.BUTTON1_MASK,
self.TARGETS,
Gdk.DragAction.DEFAULT|
Gdk.DragAction.MOVE)
self.treeview.enable_model_drag_dest(self.TARGETS,
Gdk.DragAction.DEFAULT)
self.treeview.drag_dest_add_text_targets()
self.treeview.drag_source_add_text_targets()
self.treeview.connect("drag_data_get", self.drag_data_get_data)
self.treeview.connect("drag_data_received",
self.drag_data_received_data)
self.scrolledwindow.add(self.treeview)
self.window.add(self.vbox)
self.window.show_all()
def drag_data_get_data(self, treeview, context, selection, target_id,
etime):
print("drag get data")
treeselection = treeview.get_selection()
model, iter = treeselection.get_selected()
data = bytes(model.get_value(iter, 0), "utf-8")
selection.set(selection.get_target(), 8, data)
def drag_data_received_data(self, treeview, context, x, y, selection,
info, etime):
print("received")
def main():
Gtk.main()
if __name__ == "__main__":
treeviewdndex = TreeViewDnDExample()
main()
Since I didn't find an answer to the problem in Windows, the solution was to launch the program on a Linux machine where video capture programs does not present the same effect. It was even possible to be connected to the Linux machine with VNCViewer, and make the capture in the Windows box. For demonstration purposes, it is sufficient.
But I am still wondering what prevents Gtk-Windows to receive the drop event.
Related
I'm new on Python GTK and I was developing a simple TreeView that show several data:
import gi
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk
# list of tuples for each variable, containing the environment variable name, its path, and the security category
variable_list = [
("$ENV1", "/usr/share/background/image.png", "Image"),
("$ENV2", "/home/user/script.py", "Program"),
("$ENV3", "/usr/share/icons/icon.png", "Image")
]
class TreeViewFilterWindow(Gtk.Window):
def __init__(self):
super().__init__(title="Environment Variable View")
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.variable_liststore = Gtk.ListStore(str, str, str)
for variable_ref in variable_list:
self.variable_liststore.append(list(variable_ref))
self.current_filter_category = None
# Creating the filter, feeding it with the liststore model
self.category_filter = self.variable_liststore.filter_new()
# setting the filter function, note that we're not using the
self.category_filter.set_visible_func(self.category_filter_func)
# creating the treeview, making it use the filter as a model, and adding the columns
self.treeview = Gtk.TreeView(model=self.category_filter)
for i, column_title in enumerate(
["Variable", "Path", "Category"]
):
renderer = Gtk.CellRendererText()
column = Gtk.TreeViewColumn(column_title, renderer, text=i)
self.treeview.append_column(column)
# creating buttons to filter by Category, and setting up their events
self.buttons = list()
for var_category in ["Image", "Program"]:
button = Gtk.Button(label=var_category)
self.buttons.append(button)
button.connect("clicked", self.on_selection_button_clicked)
# setting up the layout, putting the treeview in a scrollwindow, and the buttons in a row
self.scrollable_treelist = Gtk.ScrolledWindow()
self.scrollable_treelist.set_vexpand(True)
self.grid.attach(self.scrollable_treelist, 0, 0, 5, 10)
self.grid.attach_next_to(
self.buttons[0], self.scrollable_treelist, Gtk.PositionType.BOTTOM, 1, 1
)
for i, button in enumerate(self.buttons[1:]):
self.grid.attach_next_to(
button, self.buttons[i], Gtk.PositionType.RIGHT, 1, 1
)
self.scrollable_treelist.add(self.treeview)
self.show_all()
def category_filter_func(self, model, iter, data):
"""Tests if the Category in the row is the one in the filter"""
if (
self.current_filter_category is None
or self.current_filter_category == "None"
):
return True
else:
return model[iter][2] == self.current_filter_category
def on_selection_button_clicked(self, widget):
"""Called on any of the button clicks"""
# we set the current category filter to the button's label
self.current_filter_category = widget.get_label()
print("%s category selected!" % self.current_filter_category)
# we update the filter, which updates in turn the view
self.category_filter.refilter()
win = TreeViewFilterWindow()
win.connect("destroy", Gtk.main_quit)
win.show_all()
Gtk.main()
When I run the script, the content of each row cannot be selected. Is there a way to select the content of these rows in order that I can right-click and copy the content?
In case I would like to highlight with the mouse cursor the text and then directly copy the highlighted content on the clipboard with a small message saying "The content has been copied", how could I reach these purposes?
If selection was possible, this would have to be done by setting a property in Gtk.CellRendererText, but I don't see a property that would make that possible, besides editable:
renderer.set_property("editable", True)
Making the cells editable would certainly allow users to copy from them, but this would suggest the contents could be changed. (If you don't connect to the edited signal, the text reverts after the input. This might feel very counterintuitive.)
If you the time, you could maybe write your own CellRenderer (I have no experience with this)
Another possibility would be to connect to the button-released-event of the Gtk.TreeView and copy the text to the clipboard if the user right-clicks.
Use Gtk.TreeView.get_path_at_pos to get the cell under the cursor.
def listclick(self, listview, event: Gdk.EventButton, *_unused) -> bool:
"""Handler for clicks on the list."""
if (event.type == Gdk.EventType.BUTTON_RELEASE and event.button == 3):
tup = listview.get_path_at_pos(event.x, event.y)
if tup is not None:
path, col, *unused = tup
# Copy to Clipboard, depending on Path and Column
# ...
return True
return False
So I have a GTk structure like:
Window()>
Grid()>
Label()
+
ScrolledWindow()>
TreeView()
+
Box()>
Button()
Everything works fine except th Scrolled Window is not Displayed correctly as seen below. I believe i am doing something wrong with the positioning or something.
I tried playing around with the positioning numbers but can't make it work.
My Code is:
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
index = None #Global variable holding the index of the final chosen subtitle
class window(Gtk.Window):
def __init__(self,data):
Gtk.Window.__init__(self, title="SubseekerV7 R&D")
self.set_border_width(5)
self.set_default_size(200, 400)
self.grid = Gtk.Grid()
self.grid.set_column_homogeneous(True)
self.grid.set_rowndex = None
heading_text = Gtk.Label()
heading_text.set_markup('<big><b>Choose Subtitle below</b></big>\n\n<i>Select a subtitle and press Download</i>\n')
scrolled_window = Gtk.ScrolledWindow()
scrolled_window.set_border_width(5)
scrolled_window.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
self.data_list_store = Gtk.ListStore(str,str,str, str)
for item in data:self.data_list_store.append(list(item[:4]))
self.data_tree_view = Gtk.TreeView(self.data_list_store)
for i, col_title in enumerate(["Serial","Name", "Language", "Score",]):
renderer = Gtk.CellRendererText()
column = Gtk.TreeViewColumn(col_title, renderer, text=i)
self.data_tree_view.append_column(column)
scrolled_window.add_with_viewport(self.data_tree_view);
buttons_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL,spacing=10)
#Button Declarations
self.submit_button = Gtk.Button(label="Download")
self.submit_button.connect("clicked", self.select_handle)
self.cancel_button = Gtk.Button(label="Cancel")
self.cancel_button.connect("clicked", lambda x:self.destroy())
#Adding buttons to button box
buttons_box.pack_start(self.submit_button, True , True , 0)
buttons_box.pack_start(self.cancel_button, True , True , 0)
self.grid.attach(heading_text, 0, 0, 4, 1)
self.grid.attach(scrolled_window,0,1,4,4)
self.grid.attach(buttons_box,0,5,4,1)
self.add(self.grid)
def select_handle(self,widget):
global index
tree_sel = self.data_tree_view.get_selection()
(tm, ti) = tree_sel.get_selected()
index = tm.get_value(ti, 0) #Modifying the index value to the currently selected index in treeview
self.destroy()
def main():
w = window([('a'*30,'b','c','d','e'),('p'*30,'q','r','s','t')]) #Bogus test dxata
w.connect("destroy", Gtk.main_quit)
w.show_all()
Gtk.main()
if __name__ == '__main__':main()
The reason is the widget layout behavior of GTK. Widgets do not take more space than required by default. A ScrolledWindow will become invisible as it shrinks to nothing (the size of the content does not matter).
This can be solved by forcing a specific size using set_size_request(width, height), or configure the widget to grow using set_property('expand', True).
Examples:
# Setting a fixed height
scrolled_window.set_size_request(-1, 200)
# Configure the scrolled window to expand
scrolled_window.set_property('expand', True)
An alternative to a Grid is to use a Box, and set expand=True in the pack_start function.
I'm having problems to understand why my custom Gtk.CellRenderer is only rendering the first row of a Gtk.ListStore.
I've been reading many docs and trying stuff like cellrenderer.set_visible(True) but I still have no idea of why this is happening.
Here is a full example:
from gi.repository import Gtk, Gdk, cairo, Pango, PangoCairo, GObject
import time
class CellRenderer5Stars(Gtk.CellRenderer):
__gproperties__ = {
'rating': ( int, # type
"integer prop", # nick
"A property that contains an integer", # blurb
0, # min
5, # max
0, # default
GObject.PARAM_READWRITE # flags
),
}
def __init__(self):
super().__init__()
self.font_size=15
self.font="Sans Bold {}".format(self.font_size)
self.rating = 5
def do_set_property(self, pspec, value):
setattr(self, pspec.name, value)
def do_get_property(self, pspec):
return getattr(self, pspec.name)
def do_get_size(self, widget, cell_area):
return (0, 0, self.font_size*5, self.font_size+5)
def do_render(self, cr, widget, background_area, cell_area, flags):
cr.translate (0, 0)
layout = PangoCairo.create_layout(cr)
desc = Pango.font_description_from_string (self.font)
layout.set_font_description(desc)
stars_var = self.rating
for i in range(5):
if i < stars_var:
layout.set_text("★", -1)
else:
layout.set_text("☆", -1)
cr.save()
PangoCairo.update_layout (cr, layout)
cr.move_to (i*(self.font_size+1), 0)
PangoCairo.show_layout (cr, layout)
cr.restore()
GObject.type_register(CellRenderer5Stars)
class Window(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self)
self.connect('destroy', self.on_quit)
liststore = Gtk.ListStore(int)
liststore.append([3])
liststore.append([2])
liststore.append([1])
treeview = Gtk.TreeView(liststore)
treeviewcolumn = Gtk.TreeViewColumn("Rating")
treeview.append_column(treeviewcolumn)
cellrenderer = CellRenderer5Stars()
treeviewcolumn.pack_start(cellrenderer, True)
treeviewcolumn.add_attribute(cellrenderer, "rating", 0)
self.add(treeview)
self.show_all()
def on_quit(self):
Gtk.main_quit()
w = Window()
Gtk.main()
You're rendering your stars to the same place of the cairo surface for every item you pass and don't obey the cell_area you should use. Replacing
cr.move_to (i*(self.font_size+1), cell_area.y)
in your code will yield a result you'd expect. But the documentation for do_render() gives you a little more info about the spacing to use:
Invokes the virtual render function of the Gtk.CellRenderer. The three passed-in rectangles are areas in cr. Most renderers will draw within cell_area; the xalign, yalign, xpad, and ypad fields of the Gtk.CellRenderer should be honored with respect to cell_area. background_area includes the blank space around the cell, and also the area containing the tree expander; so the background_area rectangles for all cells tile to cover the entire window.
Additionally the 'destroy' signal has a window argument, so you should define on_quit(self, window) or similar instead.
i am new to python, I put a button to refresh the listview and get the information from a .txt file, but it doesn't works :(
Here is the code:
import gtk
actresses = [i.strip().replace("'", "").split() for i in open("lista.txt").readlines()]
class PyApp(gtk.Window):
def __init__(self):
super(PyApp, self).__init__()
self.set_size_request(350, 250)
self.set_position(gtk.WIN_POS_CENTER)
self.connect("destroy", gtk.main_quit)
self.set_title("ListView")
self.Boton = gtk.Button("click")
self.Boton.connect("clicked",self.create_list)
self.Boton.show()
vbox = gtk.VBox(False, 8)
sw = gtk.ScrolledWindow()
sw.set_shadow_type(gtk.SHADOW_ETCHED_IN)
sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
vbox.pack_start(sw, True, True, 0)
vbox.pack_start(self.Boton, True, True, 0)
store = self.create_model()
treeView = gtk.TreeView(store)
treeView.connect("row-activated", self.on_activated)
treeView.set_rules_hint(True)
sw.add(treeView)
self.create_columns(treeView)
self.statusbar = gtk.Statusbar()
vbox.pack_start(self.statusbar, False, False, 0)
self.add(vbox)
self.show_all()
def create_model(self):
store = gtk.ListStore(str, str, str)
for act in actresses:
store.append([act[0], act[1], act[2]])
return store
def create_columns(self, treeView):
rendererText = gtk.CellRendererText()
column = gtk.TreeViewColumn("Nombre", rendererText, text=0)
column.set_sort_column_id(0)
treeView.append_column(column)
rendererText = gtk.CellRendererText()
column = gtk.TreeViewColumn("apellido", rendererText, text=1)
column.set_sort_column_id(1)
treeView.append_column(column)
rendererText = gtk.CellRendererText()
column = gtk.TreeViewColumn("Tamaño", rendererText, text=2)
column.set_sort_column_id(2)
treeView.append_column(column)
def on_activated(self, widget, row, col):
model = widget.get_model()
text = model[row][0] + ", " + model[row][1] + ", " + model[row][2]
self.statusbar.push(0, text)
if model[row][0] == "lol":
print "Funciona"
def create_list(self, widget):
# create a TreeView object which will work with our model (ListStore)
self.listview = self.create_model()
self.treeView = gtk.TreeView(self.listview)
self.treeView.set_rules_hint(True)
PyApp()
gtk.main()
Your code has several problems.
First, the create_model method, called by create_list, is not re-reading the file, it is using the object initialized at top-level. To fix this, have create_model invoke the reading code.
More importantly, the create_list method is creating a new tree view, but isn't doing anything with it (other than storing it to a property of the Python instance). As far as the GTK window is concerned, your old tree view is still in the window. There are several ways to fix this:
As a quick fix, you can remove the existing tree view from the VBox, add the new one, and show it. A simpler version is to move the code that creates the vbox to a separate method, remove the vbox with self.remove(self.get_child()) and re-add it. Either of these will accomplish what the code set out to do.
A better fix would be to only recreate the store and switch the tree view to the new store using the set_model method.
Another possibility is to empty the existing store and re-populate it. The tree view will react to the change to the store, and it is not necessary to call set_model, nor any other hint to the tree view.
im trying to get drag and drop working well in pygobject, but it is slow and unresponsive, 90% of the time i have to wave the item i am dragging around before i can drop it successfully, can anyone see if i am doing it incorrectly or is this a bug with pygobject? here is my code
from gi.repository import Gtk, GdkPixbuf, Gdk
import os
def got_data_cb(windowid, context, x, y, data, info, time):
# Got data.
tempArray = data.get_text().splitlines()
for i in tempArray:
i = i.replace('file://','')
print i
windowid.get_model().append([i])
context.finish(True, False, time)
def drop_cb(windowid, context, x, y, time):
# Some data was dropped, get the data
windowid.drag_get_data(context, context.list_targets()[-1], time)
return True
def main():
win = Gtk.Window()
win.connect('destroy', lambda x: Gtk.main_quit())
win.set_default_size(450, 400)
amodel = Gtk.ListStore(str)
column = Gtk.TreeViewColumn()
title = Gtk.CellRendererText()
column.pack_start(title, True)
column.add_attribute(title, "text", 0)
atree = Gtk.TreeView(amodel)
atree.append_column(column)
builder = Gtk.Builder()
filename = os.path.join('', 'treeview.ui')
builder.add_from_file(filename)
abox = builder.get_object('treeview1')
atree.drag_dest_set(0, [], 0)
atree.connect('drag_motion', lambda v,w,x,y,z: True)
atree.connect('drag_drop', drop_cb)
atree.connect('drag_data_received', got_data_cb)
win.add(atree)
win.show_all()
if __name__ == '__main__':
Gtk.main()
main()
Because I haven't "treeview.ui" file, you referenced, I port the drag'n'drop treeview example of pygtk.
This might be also an example on how to convert old pygtk code to pygi. I only test this code with python3.
#!/usr/bin/env python
# example treeviewdnd.py
from gi.repository import Gtk, Gdk, Pango, GObject
class TreeViewDnDExample:
TARGETS = [
('MY_TREE_MODEL_ROW', Gtk.TargetFlags.SAME_WIDGET, 0),
('text/plain', 0, 1),
('TEXT', 0, 2),
('STRING', 0, 3),
]
# close the window and quit
def delete_event(self, widget, event, data=None):
Gtk.main_quit()
return False
def clear_selected(self, button):
selection = self.treeview.get_selection()
model, iter = selection.get_selected()
if iter:
model.remove(iter)
return
def __init__(self):
# Create a new window
self.window = Gtk.Window()
self.window.set_title("URL Cache")
self.window.set_size_request(200, 200)
self.window.connect("delete_event", self.delete_event)
self.scrolledwindow = Gtk.ScrolledWindow()
self.vbox = Gtk.VBox()
self.hbox = Gtk.HButtonBox()
self.vbox.pack_start(self.scrolledwindow, True, True, 0)
self.vbox.pack_start(self.hbox, False, True, 0)
self.b0 = Gtk.Button('Clear All')
self.b1 = Gtk.Button('Clear Selected')
self.hbox.pack_start(self.b0, True, True, 0)
self.hbox.pack_start(self.b1, True, True, 0)
# create a liststore with one string column to use as the model
self.liststore = Gtk.ListStore(str)
# create the TreeView using liststore
self.treeview = Gtk.TreeView(self.liststore)
# create a CellRenderer to render the data
self.cell = Gtk.CellRendererText()
# create the TreeViewColumns to display the data
self.tvcolumn = Gtk.TreeViewColumn('URL', self.cell, text=0)
# add columns to treeview
self.treeview.append_column(self.tvcolumn)
self.b0.connect_object('clicked', Gtk.ListStore.clear, self.liststore)
self.b1.connect('clicked', self.clear_selected)
# make treeview searchable
self.treeview.set_search_column(0)
# Allow sorting on the column
self.tvcolumn.set_sort_column_id(0)
# Allow enable drag and drop of rows including row move
self.treeview.enable_model_drag_source( Gdk.ModifierType.BUTTON1_MASK,
self.TARGETS,
Gdk.DragAction.DEFAULT|
Gdk.DragAction.MOVE)
self.treeview.enable_model_drag_dest(self.TARGETS,
Gdk.DragAction.DEFAULT)
self.treeview.drag_dest_add_text_targets()
self.treeview.drag_source_add_text_targets()
self.treeview.connect("drag_data_get", self.drag_data_get_data)
self.treeview.connect("drag_data_received",
self.drag_data_received_data)
self.scrolledwindow.add(self.treeview)
self.window.add(self.vbox)
self.window.show_all()
def drag_data_get_data(self, treeview, context, selection, target_id,
etime):
treeselection = treeview.get_selection()
model, iter = treeselection.get_selected()
data = bytes(model.get_value(iter, 0), "utf-8")
selection.set(selection.get_target(), 8, data)
def drag_data_received_data(self, treeview, context, x, y, selection,
info, etime):
model = treeview.get_model()
data = selection.get_data().decode("utf-8")
drop_info = treeview.get_dest_row_at_pos(x, y)
if drop_info:
path, position = drop_info
iter = model.get_iter(path)
if (position == Gtk.TreeViewDropPosition.BEFORE
or position == Gtk.TreeViewDropPosition.BEFORE):
model.insert_before(iter, [data])
else:
model.insert_after(iter, [data])
else:
model.append([data])
if context.get_actions() == Gdk.DragAction.MOVE:
context.finish(True, True, etime)
return
def main():
Gtk.main()
if __name__ == "__main__":
treeviewdndex = TreeViewDnDExample()
main()
In order for me to get drag n drop working from another desktop app to my app, I had to add a drag_motion_callback to copy the data into the context:
Class Window(Gtk.Window):
def __init__(self):
#other stuff before here
#Set up dragNdrop to add URIs to open draw
self.connect("drag-motion", self.motion_cb)
self.connect("drag-drop", self.drop_cb)
self.connect("drag-data-received", self.got_data_cb)
self.drag_dest_set(0, [], 0)
def motion_cb(self, wid, context, x, y, time):
Gdk.drag_status(context,Gdk.DragAction.COPY, time)
# Returning True which means "I accept this data".
return True
def drop_cb(self, wid, context, x, y, time):
wid.drag_get_data(context, context.list_targets()[-1], time)
def got_data_cb(self, wid, context, x, y, data, info, time):
#Need to do something here
files=data.get_text().rstrip('\n').split('\n')
for fn in files:
fn= fn.split('file://',1)[-1]
self.add_file(fn)
self.place_items()
print files
context.finish(True, False, time)