The following code shows that assigning an item vector to gcombobox will result in looping of the gcombobox handler over each element of the existing item vector (try clicking, for example, "a" or "b" and you'll see the multiple printed messages from the gcombobox). If b2 is changed to a gradio button then this loop doesn't happen. Also, if the tcltk toolkit is used then we don't have an issue either. This is causing problems for me in a GUI where the handler for b2 is more complex and manipulates some large data. Any suggestions to prevent this looping would be great!
options("guiToolkit"="RGtk2")
library(gWidgets)
w=gwindow()
b1=gradio(c("a","b"),container=w)
b2=gcombobox(c(1:2),container=w)
addHandlerClicked(b1,handler=function(h,...) b2[,]=c(1:10))
addHandlerClicked(b2,handler=function(h,...) print("clicked b2"))
You can block the handlers then unblock as with:
w=gwindow()
b1=gradio(c("a","b"),container=w)
b2=gcombobox(c(1:2),container=w)
id = addHandlerClicked(b2,handler=function(h,...) print("clicked b2"))
addHandlerClicked(b1,handler=function(h,...) {
blockHandler(b2, id)
b2[,]=c(1:10)
unblockHandler(b2, id)
})
I flipped the order of assignment to get the handler id.
Alternatively, in gWidgets2 (still just on Github) it just works, as this assignment of selectable items for b2 isn't setting the selected value of b2. (Which you might want to do manually)
options("guiToolkit"="RGtk2")
library(gWidgets2)
w=gwindow()
g = ggroup(cont=w) ## only one child for a gwindow instance is enforced
b1=gradio(c("a","b"),container=g)
b2=gcombobox(c(1:2),container=g, expand=TRUE)
id = addHandlerChanged(b2,handler=function(h,...) print("clicked b2"))
addHandlerChanged(b1,handler=function(h,...) {
b2[]=c(1:10)
})
Related
I am trying to do a simple GUI using Gtk with the Julia programming language, however, when I try to get the button to remove the active selection in the combobox programmatically, I get an "AssertionError".
"ERROR: AssertionError: xor(prev, current_task() !== g_stack)"
I am not sure how to get this simple example to work ?
Can anyone point me in the right direction ?
Here is my non-functional code:
using Gtk
# Create widgets------------------------------------
cb = GtkComboBoxText()
button = GtkButton("Remove Active")
# Create and Add choices to ComboBox ---------------
choices = ["zero", "one", "two", "three", "four"]
for choice in choices
push!(cb,choice)
end
# Function to get the selected choice (text) from the ComboBox
function getChoice()
i = get_gtk_property(cb, "active", Int)
return choices[i+1]
end
# Function that handles the ComboBox selection change---
function selection_changed(widget)
sel = getChoice()
println("We selected: $sel")
end
# Function to handle the button press------------------
function removeChoice(widget)
set_gtk_property!(cb,:active,-1)
end
# Connect the signals to the widgets -------------------
signal_connect(selection_changed, cb, "changed")
signal_connect(removeChoice, button, "clicked")
# Create window, and add widgets to it using Box Layout
win = GtkWindow("ComboBoxText Example",200,50)
vbox = GtkBox(:v)
push!(win, vbox)
push!(vbox, cb)
push!(vbox, button)
showall(win)
Note the warning at the end of this Gtk.jl manual page:
Warning: it is essential to avoid task switching inside Gtk callbacks, as this corrupts the Gtk C-stack. For example, use #async print or queue a message for yourself. ...
if you are still seeing segfaults in some random method due to there existing a callback that recursively calls the glib main loop (such as making a dialog box) or otherwise causes g_yield to be called, wrap the faulting code in GLib.#sigatom. This will postpone execution of that code block until it can be run on the proper stack (but will otherwise acts like normal control flow).
This is what happens here, when you try to change the selection status of the combo box from with a signal-handler callback - the "callback that recursively calls the glib main loop" as the manual page calls it.
Using either #async or Gtk.GLib.#sigatom in front of the set_gtk_property! call avoids this problem and allows the code to run.
In this case, that leads to a different error message because removeChoice itself leads to selection_change being called, and the getChoice call made there does not take into account that get_gtk_property(cb, "active", Int) could return -1. So we get a BoundsError. How you fix that depends on your use case, for demo purposes I just return nothing here in that case:
# Function to get the selected choice (text) from the ComboBox
function getChoice()
i = get_gtk_property(cb, "active", Int)
return i >= 0 ? choices[i+1] : nothing
end
# Function that handles the ComboBox selection change---
function selection_changed(widget)
sel = getChoice()
println("We selected: $sel")
end
# Function to handle the button press------------------
function removeChoice(widget)
#async set_gtk_property!(cb,:active,-1)
end
Running this, the output as I select two, then "Remove Active", then four, then "Remove Active" again in the GUI is:
julia> We selected: two
We selected: nothing
We selected: four
We selected: nothing
I'm writing out some functions for Inventory management. I've recently wanted to add a "photo url column" to my spreadsheet by using an API I've used successfully while initially building my inventory. My Spreadsheet header looks like the following:
SKU | NAME | OTHER STUFF
I have a getProductInfo function that returns a list of product info from an API I'm calling.
getProductInfo<- function(barcode) {
#Input UPC
#Output List of product info
CallAPI(barcode)
Process API return, remove garbage
return(info)
}
I made a new function that takes my inventory csv as input, and attempts to add a new column with product photo url.
get_photo_url_from_product_info_output <- function(in_list){
#Input GetProductInfo Output. Returns Photo URL, or nothing if
#it doesn't exist
if(in_list$DisplayStockPhotos == TRUE){
return(in_list$StockPhotoURL)
} else {
return("")
}
}
add_Photo_URL <- function(in_csv){
#Input CSV data frame, appends photourl column
#Requires SKU (UPC) assumes no photourl column
out_csv <- mutate(in_csv, photo =
get_photo_url_from_product_info_output(
getProductInfo(SKU)
)
)
}
return (out_csv)
}
#Call it
new <- add_Photo_URL(old)
My thinking was that R would simply input the SKU of the from the row, and put it through the double function call "as is", and the vectorized DPLYR function mutate would just vectorize it. Unfortunately I was running into all sorts of problems I couldn't understand. Eventually I figured out that API call was crashing because the SKU field was all messed up as it was being passed in. I put in a breakpoint and found out that it wasn't just passing in the SKU, but instead an entire list (I think?) of SKUs. Every Row all at once. Something like this:
#Variable 'barcode' inside getProductInfo function contains:
[1] 7.869368e+11 1.438175e+10 1.256983e+10 2.454357e+10 3.139814e+10 1.256983e+10 1.313260e+10 4.339643e+10 2.454328e+10
[10] 1.313243e+10 6.839046e+11 2.454367e+10 2.454363e+10 2.454367e+10 2.454348e+10 8.418870e+11 2.519211e+10 2.454375e+10
[19] 2.454381e+10 2.454381e+10 2.454383e+10 2.454384e+10 7.869368e+11 2.454370e+10 2.454390e+10 1.913290e+11 2.454397e+10
[28] 2.454399e+10 2.519202e+10 2.519205e+10 7.742121e+11 8.839291e+11 8.539116e+10 2.519211e+10 2.519211e+10 2.519211e+10
Obviously my initial getProductInfo function can't handle that, so it'll crash.
How should I modify my code, whether it be in the input or API call to avoid this vectorized operation issue?
Well, it's not totally elegant but it works.
I figured out I need to use lapply, which is usually not my strong suit. Initally I tried to nest them like so:
lapply(SKU, get_photo_url_from_product_info_output(getProductInfo())
But that didn't work. So I just came up with bright idea of making another function
get_photo_url_from_sku <- function(barcode){
return(get_photo_url_from_product_info_output(getProductInfo(barcode)))
}
Call that in the lapply:
out_csv<- mutate(in_csv, photocolumn = lapply(SKU, get_photo_url_from_sku))
And it works great. My speed is only limited by my API calls.
I have two QListWidgets. The user can select multiple items from one list and drag them to the other list. But within each list, some items are draggable and some are not. If the selection contains both draggable and non-draggable items, a problem happens. Only the draggable items appear in the second list, which is correct. But all the items disappear from the first list.
In the animated image above, items 00, 01, and 02 are selected. Only items 00 and 02 are drag enabled. After the drag-and-drop, all three items are gone from the first list. How can I fix this?
Here is some code to reproduce the problem:
import random
import sys
from PySide import QtCore, QtGui
class TestMultiDragDrop(QtGui.QMainWindow):
def __init__(self, parent=None):
super(TestMultiDragDrop, self).__init__(parent)
centralWidget = QtGui.QWidget()
self.setCentralWidget(centralWidget)
layout = QtGui.QHBoxLayout(centralWidget)
self.list1 = QtGui.QListWidget()
self.list1.setDragDropMode(QtGui.QAbstractItemView.DragDrop)
self.list1.setDefaultDropAction(QtCore.Qt.MoveAction)
self.list1.setSelectionMode(QtGui.QListWidget.ExtendedSelection)
self.list2 = QtGui.QListWidget()
self.list2.setDragDropMode(QtGui.QAbstractItemView.DragDrop)
self.list2.setDefaultDropAction(QtCore.Qt.MoveAction)
self.list2.setSelectionMode(QtGui.QListWidget.ExtendedSelection)
layout.addWidget(self.list1)
layout.addWidget(self.list2)
self.fillListWidget(self.list1, 8, 'someItem')
self.fillListWidget(self.list2, 4, 'anotherItem')
def fillListWidget(self, listWidget, numItems, txt):
for i in range(numItems):
item = QtGui.QListWidgetItem()
newTxt = '{0}{1:02d}'.format(txt, i)
if random.randint(0, 1):
item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
else:
# If the item is draggable, indicate it with a *
newTxt += ' *'
item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsDragEnabled)
item.setText(newTxt)
listWidget.addItem(item)
def openMultiDragDrop():
global multiDragDropUI
try:
multiDragDropUI.close()
except:
pass
multiDragDropUI = TestMultiDragDrop()
multiDragDropUI.setAttribute(QtCore.Qt.WA_DeleteOnClose)
multiDragDropUI.show()
return multiDragDropUI
if __name__ == '__main__':
app = QtGui.QApplication([])
openMultiDragDrop()
sys.exit(app.exec_())
Here I have some suspicion on setDefaultDropAction(QtCore.Qt.MoveAction)
Read below para from documentation: Specially the bold line
In the simplest case, the target of a drag and drop action receives a copy of the data being dragged, and the source decides whether to delete the original. This is described by the CopyAction action. The target may also choose to handle other actions, specifically the MoveAction and LinkAction actions. If the source calls QDrag::exec(), and it returns MoveAction, the source is responsible for deleting any original data if it chooses to do so. The QMimeData and QDrag objects created by the source widget should not be deleted - they will be destroyed by Qt.
(http://doc.qt.io/qt-4.8/dnd.html#overriding-proposed-actions)
First give a try with QtCore.Qt.CopyAction
Second, if MoveAction is mandatory, try creating QMimeData and QDrag objects in your source list widget's mouseMoveEvent.
Here in below link, you can find some help for creating QMimeData and QDrag objects in your source list widget's mouseMoveEvent. (code is in C++, My intention is to get conceptual idea).
http://doc.qt.io/qt-4.8/dnd.html#overriding-proposed-actions
I think Kuba Ober is right that this is a Qt bug. In the C++ source code, there is a function void QAbstractItemViewPrivate::clearOrRemove(). It deletes all selected rows, but it does not look at whether each item is drag-enabled or not.
That being the case, I came up with a few workarounds:
Method 1: Make all non-draggable items non-selectable as well
This is the easiest method. Just remove the QtCore.Qt.ItemIsEnabled flag from all non-draggable items. Of course if you want all of your items to be selectable, this won't work.
Method 2: Recreate the "startDrag" function
Since the clearOrRemove function belongs to a private class, I cannot override it. But that function is called by the startDrag function, which can be overridden. So I essentially duplicated the function in Python and replaced the call to clearOrRemove with my own function removeSelectedDraggableItems.
The problem with this method is that startDrag contains calls to a few other functions belonging to a private class. And those functions call other private class functions. Specifically, these functions are responsible for controlling how the items are drawn during the drag event. Since I didn't want to recreate all the functions, I just ignored those. The result is that this method results in the correct functionality, but it loses the graphical indication of which items are being dragged.
class DragListWidget(QtGui.QListWidget):
def __init__(self):
super(DragListWidget, self).__init__()
def startDrag(self, supportedDragActions):
indexes = self.getSelectedDraggableIndexes()
if not indexes:
return
mimeData = self.model().mimeData(indexes)
if not mimeData:
return
drag = QtGui.QDrag(self)
rect = QtCore.QRect()
# "renderToPixmap" is from a private class in the C++ code, so I can't use it.
#pixmap = renderToPixmap(indexes, rect)
#drag.setPixmap(pixmap)
drag.setMimeData(mimeData)
# "pressedPosition" is from a private class in the C++ code, so I can't use it.
#drag.setHotSpot(pressedPostion() - rect.topLeft())
defaultDropAction = self.defaultDropAction()
dropAction = QtCore.Qt.IgnoreAction
if ((defaultDropAction != QtCore.Qt.IgnoreAction) and
(supportedDragActions & defaultDropAction)):
dropAction = defaultDropAction
elif ((supportedDragActions & QtCore.Qt.CopyAction) and
(self.dragDropMode() != self.InternalMove)):
dropAction = QtCore.Qt.CopyAction
dragResult = drag.exec_(supportedDragActions, dropAction)
if dragResult == QtCore.Qt.MoveAction:
self.removeSelectedDraggableItems()
def getSelectedDraggableIndexes(self):
""" Get a list of indexes for selected items that are drag-enabled. """
indexes = []
for index in self.selectedIndexes():
item = self.itemFromIndex(index)
if item.flags() & QtCore.Qt.ItemIsDragEnabled:
indexes.append(index)
return indexes
def removeSelectedDraggableItems(self):
selectedDraggableIndexes = self.getSelectedDraggableIndexes()
# Use persistent indices so we don't lose track of the correct rows as
# we are deleting things.
root = self.rootIndex()
model = self.model()
persistentIndices = [QtCore.QPersistentModelIndex(i) for i in selectedDraggableIndexes]
for pIndex in persistentIndices:
model.removeRows(pIndex.row(), 1, root)
Method 3: Hack "startDrag"
This method changes the drop action from "MoveAction" to "CopyAction" before calling the built-in "startDrag" method. Then it calls a custom function for deleting the selected drag-enabled items. This solves the problem of losing the graphical dragging animation.
This is a pretty easy hack, but it comes with its own problem. Say the user installs an event filter that changes the drop action from "MoveAction" to "IgnoreAction" in certain cases. This hack code doesn't get the updated value. It will still delete the items as though the action is "MoveAction". (Method 2 does not have this problem.) There are workarounds for this problem, but I won't go into them here.
class DragListWidget2(QtGui.QListWidget):
def startDrag(self, supportedDragActions):
dropAction = self.defaultDropAction()
if dropAction == QtCore.Qt.MoveAction:
self.setDefaultDropAction(QtCore.Qt.CopyAction)
super(DragListWidget2, self).startDrag(supportedDragActions)
if dropAction == QtCore.Qt.MoveAction:
self.setDefaultDropAction(dropAction)
self.removeSelectedDraggableItems()
def removeSelectedDraggableItems(self):
# Same code from Method 2. Removed here for brevity.
pass
It is a standard QTableWidget
All cells are QTableWidgetItem.
All cells are editable/selectable
Question: How can I modify all the cells I have selected?
Possible way is to use the dialog open. So the idea is like this :
Select the items
Make a button or something to open input dialog.
Apply the value of input dialog to all selected items.
I was facing a similar problem a couple years ago and I solved it like this:
I have inherited my own view and I have reimplemented methods commitData() and mouseReleaseEvent().
commitData takes all selected indices from the selection model and calls QAbstractItemModel::setData() for all of them. Data are taken from the editor like this:
QByteArray n = editor->metaObject()->userProperty().name();
if (n.isEmpty())
n = delegate->itemEditorFactory()->valuePropertyName(model()->data(index, Qt::EditRole).userType());
if (!n.isEmpty())
{
QVariant data = editor->property(n);
for (const QModelIndex & idx : selectedIndices)
{
model()->setData(idx, data);
}
}
mouseReleaseEvent() performs three steps:
Get a current selection from the selection model.
Call original event handler (QTableWidget::mouseReleaseEvent())
Restore selection: QItemSelectionModel::select()
I am trying to concatenate multiple options from a gdroplist to a vector. When I run this code on console, it works fine. But it won't work when I want it to work on a button press.
leftlist<-vector()
left<-gdroplist(itemsnames,container = g2_mba)
addleft<-function(h,...){
s<-svalue(left)
c(leftlist,s, recursive=TRUE)
}
b5<-gbutton(text=" Add to left", container = g2_mba, handler = addleft)
I have also tried append method for a vector but the vector won't update on the button click. Any suggestions?