I'm just starting to piece together a simple utility for turning on a projector and selecting some presets on it. I was using it from a command line but then thought it would be a good excuse to start learning Tkinter. I'm struggling with the OOP design, since clearly you can do this with functions and so on. As I started adding more features and buttons, it seemed kind of crazy to have a particular callback to a one off function for each button. How do engineers deal with this?
If you have simillar buttons with simillar functions you can use loop (for) to create that buttons and you can use one function with different argument. You have to use lambda function to call function with arguments.
(not complet) example:
def my_func(a, b):
print a, b
Button("Hello", command=lambda arg1="abc",arg2=123:my_func(arg1, arg2)).pack()
Button("World", command=lambda arg1="xyz",arg2=987:my_func(arg1, arg2)).pack()
You can even use list to keep arguments for all buttons.
def my_func(a, b):
print a, b
buttons = (
# title, x, y, function name, function arguments)
("Hello", 0, 0, my_func, ("abc", 123)),
("World", 0, 1, my_func, ("xyz", 987)),
)
for btn in buttons:
title, x, y, func_name, func_args = btn
temp = Button(title, command=lambda func=func_name, args=func_args:func(*args) )
temp.grid(row=y, column=x)
Related
I want to create two ipywidget sliders, say one with value x, the other with value 1-x. When I change one slider, the other one should be automatically changed acccordingly. I am trying to use observe for callback. I see that I might use owner and description to identify which slider was modified. But I don't think description supposed to be used for this purpose. After all, description should not need to be unique in the first place. I wonder if I am missing something here.
from ipywidgets import widgets
x=0.5
a=widgets.FloatSlider(min=0,max=1,description='a',value=x)
b=widgets.FloatSlider(min=0,max=1,description='b',value=1-x)
display(a,b)
def on_value_change(change):
if str(change['owner']).split("'")[1]=='a':
exec('b.value='+str(1-change['new']))
else:
exec('a.value='+str(1-change['new']))
a.observe(on_value_change,names='value')
b.observe(on_value_change,names='value')
Beginner with widgets, but I ran into the same question earlier and couldn't find a solution. I pieced together several sources and came up with something that seems to work.
Here's a model example of two sliders maintaining proportionality according to '100 = a + b', with two sliders representing a and b. :
caption = widgets.Label(value='If 100 = a + b :')
a, b = widgets.FloatSlider(description='a (=100-b)'),\
widgets.FloatSlider(description='b (= 100-a)')
def my_func1(a):
# b as function of a
return (100 - a)
def my_func2(b):
# a as function of b
return (100 - b)
l1 = widgets.dlink((a, 'value'), (b, 'value'),transform=my_func1)
l2 = widgets.dlink((b, 'value'), (a, 'value'),transform=my_func2)
display(caption, a, b)
To explain, as best as I understand... the key was to set up a directional link going each direction between the two sliders, and to provide a transform function for the math each direction across the sliders.
i.e.,:
l1 = widgets.dlink((a, 'value'), (b, 'value'),transform=my_func1)
What that is saying is this: .dlink((a, 'value'), (b, 'value'),transform=my_func1) is like "the value of a is a variable (input) used to determine the value of b (output)" and that "the function describing b, as a function of a, is my_func1".
With the links described, you just need to define the aforementioned functions.
The function pertaining to direct link l1 is:
def my_func1(a): # defining b as function of a
return (100 - a)
Likewise (but in reverse), l2 is the 'vice versa' to l1, and my_func2 the 'vice versa' to my_func1.
I found this to work better for learning purposes, compared to the fairly common approach of utilizing a listener (a.observe or b.observe) to log details (e.g. values) about the states of the sliders into a dictionary-type parameter (change) which can be passed into the transform functions and indexing for variable assignments.
Good luck, hope that helps! More info at https://ipywidgets.readthedocs.io/en/latest/examples/Widget%20Events.html#Linking-Widgets
Prelude
ComputerCraft is a mod for Minecraft (Forge) that adds a crude lua-based Computer to the game. Using this Computer, one can write programs to interact with the Minecraft world in various ways. Whether a ComputerCraft question is applicable to StackOverflow has been previously debated in other questions, but I believe it is applicable, as the mod is, for the most part, about programming, and while some ComputerCraft proprietary API calls are made, there is no concept in this question that would not apply to other, non-ComputerCraft-related lua programs (unless of course the problem is caused by a bug in ComputerCraft itself). Documentation for the used APIs can be found at http://www.computercraft.info/wiki/Category:APIs.
Note: Do not be alarmed if you have no ComputerCraft experience; I believe that this issue may be completely unrelated to ComputerCraft, and instead be caused by some intricacy of OOP in lua that I have failed to grasp. I have commented the code where I felt it necessary to explain the most important aspects of the proprietary calls I am making. If anything is unclear, please comment and I will clarify.
If you want to be able to run the code examples without Minecraft, there is an excellent ComputerCraft emulator available called CCEmuRedux. I have tested my code on both actual ComputerCraft and CCEmuRedux with identical results, although CCEmuRedux doesn't seem to support Monitors. An "Advanced" Computer is necessary to see the colours.
Problem
In ComputerCraft 1.75 (and CCEmuRedux # ComputerCraft 1.79), given the following class gui, and a test program that attempts to draw a rudimentary button in each of two different windows using the gui class, both buttons are drawn in the second window. Graphically, the result of guiTest.lua is https://i.imgur.com/llFDlYI.png, while I would expect the first (orange) button to be drawn in Window 1. While I have some theories as to why it behaves this way, I don't have the necessary lua experience to figure out how to fix it. This is an MWE.
Code example
gui.lua
--Meta class
gui = {t, vpx, vpy}
function gui:new(t, title) -- I'm aware this constructor is not in keeping with the referenced Tutorialspoint article, it is of no consequence in this example
local o = o or {}
setmetatable(o, self)
self.__index = self
self.t = t
local sX, sY = self.t.getSize() -- get the size of the virtual terminal and save it to vpx, vpy
self.vpx = sX
self.vpy = sY
self.t.setCursorPos(1, 1) -- put cursor at the start of the virtual terminal
self.t.write(tostring(title)) -- note that this WORKS, it prints one title per Window as seen in the screenshot
return o
end
function gui:drawButton(x, y, sX, sY, colour)
self.t.setCursorPos(x, y) -- set the cursor to the button's first x- and y-coords
self.t.setTextColor(colours.black) -- set text colour to black
self.t.setBackgroundColor(colour) -- set background colour to the colour of the button
for iY = 1, sY do
for iX = 1, sX do
self.t.write("#") -- print hashtags to represent the button until we reach sX and sY
end
self.t.setCursorPos(x, y + iY) -- move cursor a line down, and back to button's first x-coord
end
self.t.setCursorPos(self.vpx, self.vpy) -- get cursor out of the way so the screenshot will be prettier
end
guiTest.lua
dofile('gui.lua')
local w1 = window.create(term.current(), 2, 2, 22, 15)
local w2 = window.create(term.current(), 26, 2, 22, 15) -- creates virtual windows in a terminal, acting as terminals of their own
-- window.create() arguments: terminal object to create window on, x position, y position, x size, y size
local g1 = gui:new(w1, "Window 1") -- create gui object for the first window
local g2 = gui:new(w2, "Window 2") -- create gui object for the second window
g1:drawButton(5, 3, 3, 2, colours.orange) -- should draw in w1, draws in w2
g2:drawButton(10, 8, 4, 4, colours.green) -- should draw in w2, draws in w2
Attempted solutions
For what it's worth, I've been following the Lua OOP recipe # https://www.tutorialspoint.com/lua/lua_object_oriented.htm. This is my second lua-based program, so I expect it to be an "easy" problem. I have more than a basic understanding of how OOP works in several other languages (particularly Java), though, and as such my programmer's "Spidey-Sense" is telling me that either some variable, such as t, isn't "local enough" (same variable gets used by both windows), or some reference in one of the gui objects gets overwritten when a new gui object gets created.
Therefore, I tried making the table gui local, to ensure it was not being overwritten:
local gui = {t, vpx, vpy}
... but it spat an error attempt to index ? on line 6 of "gui.lua" (setmetatable(o, self)), so instead I tried (realising that I would be unable to access the function from outside gui.lua, due to it being local):
local function gui:drawButton(x, y, sX, sY, colour)
... which resulted in guiTest.lua:1: bios.lua:14 [string "gui.lua"]:17:'(' expected. Line 17 is the definition of gui:drawButton() in the code tag above. In my admittedly limited ComputerCraft experience, such poorly formatted error messages generally mean that the lua interpreter or CraftOS is Exceptionally Confused™, but I assume the gist of it is "you can't make an object method local", as I can make other functions local in a similar fashion to what I've tried here.
It is not a problem with window.create() or with using the window API in general, as the same thing happens when using separate Monitors instead of just separate windows on the same Monitor. Essentially:
dofile('gui.lua')
local w = window.create(term.current(), 2, 2, 22, 15)
local m = peripheral.wrap('top') -- m becomes the Monitor physically on top of the ComputerCraft Computer
local gw = gui:new(w, "Window") -- create gui object for the Window
-- m is a terminal object, just like w, so we can still do
local gm = gui:new(m, "Monitor") -- create gui object for the Monitor
gw:drawButton(5, 3, 3, 2, colours.orange) -- should draw in w, draws in m
gm:drawButton(10, 8, 4, 6, colours.green) -- should draw in m, draws in m
Perhaps there is a way of storing the function as a local variable, along the lines of
local gui:printFoo = function() print("foo") end
self:printFoo() -- prints "foo"...?
... or perhaps more likely, the issue is something I have entirely missed.
Conclusion
To make a long question short, defining two gui objects, one for each of two virtual console windows, and attempting to draw one button on each of the virtual console windows using their respective gui objects, results in both buttons being drawn on the same virtual console window. Why?
Yes, OOP in Lua is hard for Lua beginners, despite of excellent knowledge of OOP languages (such as Java).
--Meta class
gui = {} -- class is a global variable, no default properties exist
function gui:new(t, title) -- t = window, self = your class "gui"
local o = {} -- creating NEW object
setmetatable(o, self) -- link the object with the class
self.__index = self
o.t = t -- save window into object (not into class)
local sX, sY = t.getSize() -- get the size of the virtual terminal
o.vpx = sX -- save window's properties into object (not into class)
o.vpy = sY
t.setCursorPos(1, 1)
t.write(tostring(title))
return o
end
function gui:drawButton(x, y, sX, sY, colour) -- self = object
....
end
I want a bunch of buttons, using QtGui to all have their own unique values, but when looping to create a grid of them, the button variable is overwritten.
I was trying to get something that would have each button have its own variable, like grid_btn01, grid_btn02, and so on.
Ideally, it would be like this
for x in range(gridx):
grid_btn + str(x) = GridBtn(self, x, y, btn_id)
But of course, this doesn't work.
consider using a python dictionary
,also i'm not familiar with Qt but double check what is the return value of this function maybe btn_id is the variable you should store
buttons = {}
for x in range(gridx):
buttons[x] = GridBtn(self,x,y,btn_id)
What you're asking may technicallybe possible in Python, but it's definitely the wrong approach.
Use a list instead:
grid_btns = []
for x in range(gridx):
y = ...
grid_btns.append(GridBtn(self, x, y, btn_id))
Is there currently a way to add plot elements together in Gadfly.jl?
For example, in R if I have another function that returns a ggplot and I want to add a title to it, I'd do the following:
p <- makeMyPlot()
p + ggtitle("Now it has a title")
Is there currently a Gadfly equivalent? If not, is this on Gadfly's roadmap?
There is add_plot_element(), which can add stuff to an existing layer:
xs = [0:0.1:pi]
l = layer(x=xs, y=sin(xs))
add_plot_element(l, Guide.title("Now it has a title"))
You can then plot the layer using plot(l), and invoke either draw or display to actually show something. Further down, there's a bunch of overloads that work on a Plot directly:
p = plot(x=xs, y=sin(xs))
add_plot_element(p, Guide.title("Now it has a title"))
display(p)
I can't find either of these functions in the documentation, but fortunately the source is comprehensible enough. One of the many joys of Julia =)
In the Gimp GUI, the QuickMask is very useful for many things, but this functionality doesn't seem to be directly available through script-fu. No obvious equivalents were apparent to me in the procedure browser.
In particular, putting the (value/gray) pixels of a layer into the selection mask is the basic thing I need to do. I tried using gimp-image-get-selection to get the selection channel's id number, then gimp-edit-paste into it, but the following anchor operation caused Gimp to crash.
My other answer contains the "theoretical" way of doing it - however, the O.P. found a bug in GIMP, as of version 2.6.5, as can be seem on the comments to that answer.
I got a workaround for what the O.P. intends to do: paste the contents of a given image layer to the image selection. As denoted, edit-copy -> edit-paste on the selection drawable triggers a program crash.
The workaround is to create a new image channel with the desired contents, through the copy and paste method, and then use gimp-selection-load to make the selection equal the channel contents:
The functions that need to be called are thus (I won't paste scheme code, as I am not proficient in all the parenthesis - I did the tests using the Python console in GIMP):
>>> img = gimp.image_list()[0]
>>> ch = pdb.gimp_channel_new(img, img.width, img.height, "bla", 0, (0,0,0))
>>> ch
<gimp.Channel 'bla'>
>>> pdb.gimp_edit_copy(img.layers[0])
1
>>> pdb.gimp_image_add_channel(img, ch, 0)
>>> fl = pdb.gimp_edit_paste(ch, 0)
> >> fl
<gimp.Layer 'Pasted Layer'>
>>> pdb.gimp_floating_sel_anchor(fl)
>>> pdb.gimp_selection_load(ch)
Using QuickMask through the User interface is exactly equivalent to draw on the Selection, treating the selection as a drawable object.
So, to use the equivalent of "quickmask" on script-fu all one needs to is to retrieve the Selection as a drawable and pass that as a parameter to the calls that will modify it -
And to get the selection, one just have to call 'gimp-image-get-selection'