Auto-generating widgets in awesome-wm - awesome-wm

So what I currently want to do is pretty much implement rofi in awesome.
The reason I want to do this and I don't just use rofi is because I want to learn how to 'auto-generate' widgets in awesome.
This will come in handy later when I'll implement things like network widgets that when clicked, shows you a panel, shows you the wifi hotspots available as rows, etc etc. So it's just for me to learn how awesome works better. But also, I want a program launcher.
And also, before someone suggests it, I already know that there's a built-in launcher in awesome, and I also know that there's this. This is not what I'm looking for. I want to have the same thing thing rofi and dmenu have: I want to have suggestions pop up when you press keys. and I want to be able to click on the suggestions, etc.
What I want is something like this: uhhhh
So what I'm having problems is this: how do I auto-generate the rows? I want to be able to specify in only one place how many rows I want, and have awesome do the rest.
I've looked through Elv's github and I found radical and even though what he made is a menu system, I thought that I could use some of his code to do what I want. But I can't for the love of god figure out how it works. No offense to him, but it's not all too well docummented, even for users, and for actually explaining how the code works there's no docummentation.
So My question is: How can I make this work? How would I go about making the widgets that act as the rows automatically?
TL;DR:
i want to write a program launcher like rofi in awesome
i want to be able to specify only in one place the number of rows
therefore, (((I think))) I need to automatically generate widgets as rows somehow, how can I do it?
EDIT:
What I want is to be able to create the rows of my launcher automatically. I know I can hardcode the rows myself, have each row have a different id and then I can write a function that on each keypress, will update each widget with the most relevant matches. So it would be something like (not tested at all):
local wibox = require("wibox")
local awful = require("awful")
local num_rows = 10
local row_height = 40
-- set the height of the background in accordance to how many rows there are,
-- and how high each row should be
local prompt_height = row_height * num_rows
local prompt_width = 300
-- make a widget in the middle of the screen
local background = wibox({
x = awful.screen.focused().geometry.width / 2 - prompt_width / 2,
y = awful.screen.focused().geometry.height / 2 - prompt_height / 2,
width = prompt_width,
height = prompt_height,
bg = "#111111",
visible = false,
ontop = false
})
local rofi_launcher = wibox.widget({
widget = background,
{
-- get a flexible layout so the searchbox and the suggestion boxes get
-- scaled to take up all the space of the background
layout = wibox.layout.flex.vertical,
{ -- the prompt you actually type in
-- set id here so we can adjust its ratio later, so the magnifying
-- glass will end up on the right, and the texbox will take up the left side
id = "searchbox_and_mangifying_glass",
layout = wibox.layout.ratio.horizontal,
{
-- set id so we can use it as a prompt later
id = "searchbox",
widget = wibox.widget.textbox,
},
{
widget = wibox.widget.imagebox,
icon = '~/path/to/magnifying_glass_icon.svg',
},
},
{ -- this is where I actually create the rows that will display suggestions
{ -- row number 1
-- make a background for the textbox to sit in, so you can change
-- background color later for the selected widget, etc etc.
widget = wibox.widget.background,
{
-- give it an id so we can change what's displayed in the
-- textbox when we press keys in the prompt
id = "suggestion_1",
widget = wibox.widget.textbox,
},
},
{ -- row number 2
-- background, again
widget = wibox.widget.background,
{
-- id and textbox again
id = "suggestion_2",
widget = wibox.widget.textbox,
},
},
-- and another 8 (according to the `num_rows` variable) of the same two
-- textboxes above. This is exactly my problem. How can I make these
-- textboxes automatically and still be able to interact with them to
-- display suggestions on the fly, as the user types keys into the prompt?
},
},
})
If this is not clear enough please do let me know what you don't understand and I will update my question.

Equally untested as your code, but this creates a tables of textboxes instead of using the declarative layout to create all these textboxes:
[SNIP; For shorter code I removed some stuff at the beginning]
local textboxes = {}
local widgets = {}
for i = 1, num_rows do
local tb = wibox.widget.textbox()
local bg = wibox.widget.background(tb)
bg:set_bg("#ff0000") -- The original code did not set a bg color, but that would make the bg widget useless...?
tb.id = "suggestion_" .. tostring(i) -- This is likely unnecessary, but the original code set these IDs, too
table.insert(textboxes, tb)
table.insert(widgets, bg)
end
local rofi_launcher = wibox.widget({
widget = background,
{
-- get a flexible layout so the searchbox and the suggestion boxes get
-- scaled to take up all the space of the background
layout = wibox.layout.flex.vertical,
{ -- the prompt you actually type in
[SNIP - I did not change anything here; I only removed this part to make the code shorter]
},
widgets
},
})
-- Now make the textboxes display something
textboxes[3].text = "I am the third row"
textboxes[5].text = "I am not"

Related

Maximize client window vertically to the half left of screen

How to configure a shortcut key in awesome to toggle a client window vertical maximization to the left half of the screen (snap to left)?
Module awful.placement has an example that may help, but there is no mention on how to implement a toggle that would be able to maximize the client or restore it to its prior size and location.
Currently I have the following in rc.lua:
clientkeys = gears.table.join(
-- ...
awful.key({ modkey, "Mod1" }, "Left",
function (c)
-- Simulate Windows 7 'edge snap' (also called aero snap) feature
local f = awful.placement.scale + awful.placement.left + awful.placement.maximize_vertically
f(c.focus, {honor_workarea=true, to_percent = 0.5})
end ,
{description = "maximize vertically to the left half of screen", group = "client"})
)
Are you looking for awful.placement.restore? It seems to do be what you are looking for. However, the documentation says one has to "set[...] the right context argument" for this, but does not mention which one that is.
I think it should be scale since it is the first one in your chain, but I fail to see the logic in calling this chain "scale".
To turn that into a toggle, you can "invent" a new client property. Something like this: if c.my_toggle then print("a") else print("b") end c.my_toggle = not c.my_toggle. This way, the my_toggle property tracks which function you have to call.

Is there a way to jump to last edited cell in Jupyter?

Often in Jupyter I'd move to different parts of the notebook to look at something, and when I am done I want to jump back to where I was working on previously. Right now I'd have to navigate to the closest Markdown section (through the Jupyter Notebook Extensions) and move up or down to get to where I was. Is there a way to jump directly to the last cell that I have made an edit (preferably through keyboard shortcut)? Thanks!
Ideally this would be a built-in shortcut of course, but in the meantime:
Option 1: Custom JavaScript
If you get a browser extension like Custom JavaScript for Websites 2 (open-source), then you can use this code to record a stack of scroll position histories and jump backwards with Ctrl+Shift+X:
// Visit JupyterLab in browser and click the Custom JS browser extension icon and then paste this:
if(location.href.startsWith("http://localhost:8888/lab")) {
let scrollLocationsHistories = new Map();
let scrollBinHeight = 100;
window.addEventListener("keydown", (e) => {
let notebookEl = document.querySelector(".jp-mod-searchable .jp-NotebookPanel-notebook");
if(!scrollLocationsHistories.has(notebookEl)) scrollLocationsHistories.set(notebookEl, [])
let scrollLocationsHistory = scrollLocationsHistories.get(notebookEl);
if(e.ctrlKey && e.shiftKey && e.key === "X") {
if(scrollLocationsHistory.length > 0) {
e.preventDefault();
let origScrollPos = notebookEl.scrollTop;
notebookEl.scrollTo(0, scrollLocationsHistory.pop()*scrollBinHeight);
let newScrollPos = notebookEl.scrollTop;
if(Math.abs(origScrollPos-newScrollPos) < scrollBinHeight && scrollLocationsHistory.length > 0) {
notebookEl.scrollTo(0, scrollLocationsHistory.pop()*scrollBinHeight); // jump back again because last edit position was close to current position
}
console.log("Scroll History (newest locations at end):", scrollLocationsHistory.map(v => v*scrollBinHeight))
}
} else if(!e.ctrlKey && !e.shiftKey && document.activeElement.tagName.toLowerCase() === "textarea") {
let scrollBin = Math.round(notebookEl.scrollTop/scrollBinHeight);
if(scrollLocationsHistory[scrollLocationsHistory.length-1] !== scrollBin) {
scrollLocationsHistory.push(scrollBin);
if(scrollLocationsHistory.length > 500) scrollLocationsHistory = scrollLocationsHistory.slice(-250);
}
}
});
}
It's just an initial prototype, but it seems to work quite well so far. You may want to adjust it a bit - e.g. scrollBinHeight causes nearby edits that are within scrollBinHeight pixels of one another to not create a second history entry. You'll need to edit http://localhost:8888/lab to match the URL that you want to enable it on. If you're reading this long after I've written it, then you may also need to change document.querySelector(".jp-mod-searchable .jp-NotebookPanel-notebook") (i.e. the main scrolling element of the active notebook) in case they've updated the HTML class names, or HTML structure.
Option 2: Fold Often
Another possible option (which may be impractical depending on your use case) is to get used to folding cells that you're not currently working on. That makes it much easy to quickly scroll between cells that you're working on.
Option 3: Search Hack
If you're working on a particular cell but often have to jump to another one, you can add a comment like #vv (or any random easy-to-type string) to both of those cells and then whenever you need to jump between them, just press Ctrl+F and then Enter. The first time you do this you'll obviously need to type vv in the search box, but after that it'll be remembered (unless you use the search for another string). The disadvantage of this approach is that you need to "prune" the #vvs from cells that you're no longer working on.
echap to go to command mode, then Ctrl + z will undo your last change, which will bring the focus on the last edited cell. ctrl + y will redo the last modification.
(Only tested on python3 kernel)
EDIT Actually if you press ctrl + z just once, you only get the focus part, without modifying your cell. Then press enter to go to edit mode, which scrolls the page to the active cell.

Leaflet js: How to count the layers in Layer Group?

I would like to make such a map that only allows checking/showing 2 layers at a time and it should show some warning if chosen more.
After searching the internet, and taking a look at these possible solutions and references: first, second, third etc.
...I still can't get my code to do this simple thing: to count the layers in a layer group and according to that, to show a message.
Here my code:
var NumbActive = new L.layerGroup();
NumbActive.addTo(map);
map.on('overlayadd',function(active){
if (active.layer===LAY1){
NumbActive.addLayer(LAY1);
}});
map.on('overlayadd',function(active){
if (active.layer===LAY2){
NumbActive.addLayer(LAY2);
}});
map.on('overlayadd',function(active){
if (active.layer===LAY3){
NumbActive.addLayer(LAY3);
}});
size = NumbActive.getLayers().length;
var options = { timeout: 5000 };
var box = L.control.messagebox(options).addTo(map);
if (size >2){
box.show( 'Please choose only 2 layers at the same time!' );
};
I am also using this plugin for showing messages, which normally works fine, but in this case not at all.
I thought that layerGroup.getLayers().length should give a number as a result (length of an array)? It should work but somehow it doesn't.
Please tell me your suggestions. It can't be that complicated. I would just like to know if I'm making some minor mistake that's possible to be corrected or should I write some new function/search for a plugin.
Thanks in advance! :)

Get Geometry of widgets with variable size

Situation
I have a pop_up widget (say a textbox), which I can place arbitrarily on the screen by setting the properties x and y accordingly.
On the other hand I use the prompt, which is located in the default wibar.
I would like to place the pop_up widget directly below the prompt
Problem
I was not yet able to gather any useful information about the geometry of the prompt. With geometry I mean its x and y values together with its height and width. I solved the y-positioning by using the height of the wibar itself.
But I am stuck with x-positioning.
Is there a way to get the width of the widgets within the toolbar?
Notice
I read something about forced_width, but in this situation it sounds like a hack to me. So I would prefer to avoid forcing any widths.
I'm currently running awesome WM 4.2 on Fedora 26
Part of a problem is that "a" widget does not have a position and size since awesome allows widgets to be shown in multiple places at once. However, if we just ignore this problem, something like the following could work (to be honest: I did not test this):
function find_widget_in_wibox(wb, widget)
local function find_widget_in_hierarchy(h, widget)
if h:get_widget() == widget then
return h
end
local result
for _, ch in ipairs(h:get_children()) do
result = result or find_widget_in_hierarchy(ch, widget)
end
return result
end
local h = wb._drawable._widget_hierarchy
return h and find_widget_in_hierarchy(h, widget)
end
However, I have to warn you that the above could break in newer versions of awesome since it access non-public API (the part with wb._drawable._widget_hierarchy). There is a way to work with just the public API using :find_widgets(), but I am too lazy for that for now.
The above function gets the wibox.hierarchy instance representing a widget which allows to get the geometry of the prompt via something like the following (in the default config of awesome 4.2):
local s = screen.primary -- Pick a screen to work with
local h = find_widget_in_wibox(s.mywibox, s.mypromptbox)
local x, y, width, height = h:get_matrix_to_device()
:transform_rectangle(0, 0, h:get_size())
local geo = s.mywibox:geometry()
x, y = x + geo.x, y + geo.y
print(string.format("The widget is inside of the rectangle (%d, %d, %d, %d) on the screen", x, y, width, height)
Finally, note that the widget hierarchy is only updated during repaints. So, during startup the code above will fail to find the widget at all and right after something changed (e.g. you entered another character into the promptbox), the above will still "figure out" the old geometry.

Purpose of packStart in GTK Box development

I've tried to understand when the packStart function is useful to use but, I do not understand it. What does it do? I tried looking for the documentation but, I haven't had much luck.
Here a snip of the code I am working with.
# add a frame for aesthetic purposes
frame = gtkFrameNew("Specify data location...")
window$add(frame)
# create and add a new vertical container box
vbox = gtkVBoxNew(FALSE , 8)
vbox$setBorderWidth(24)
frame$add(vbox)
# create a horizontal container
hbox = gtkHBoxNew(FALSE , 8)
vbox$packStart(hbox , FALSE , FALSE , 0)
label = gtkLabelNewWithMnemonic("_File name")
vbox$packStart(label , FALSE , FALSE , 0)
If your R bindings don't come with the GTK+ documentation, you'll need to fall back to the regular GTK+ documentation (which uses C).
gtk_box_pack_start() is one of the ways you can add a widget to a GtkBox. The first argument is the widget to add.
The next two arguments describe how the widget should grow when the box itself grows. They are called expand and fill, respectively. For a discussion of expanding and filling widgets, see this.
The final argument just says how much space is between this widget and the next one you add to the container.
What they mean by pack start is that the widget are added in order, with the first widget appearing at the top of the box. For instance, if I do
vbox$packStart(widget1 , FALSE , FALSE , 0)
vbox$packStart(widget2 , FALSE , FALSE , 0)
vbox$packStart(widget3 , FALSE , FALSE , 0)
the box will look like
-------
widget1
widget2
widget3
-------
There are other ways to add controls to a GtkBox. pack_end() works similarly to pack_start(), except the controls are added backwards to the bottom of the box. add() (gtk_container_add()) behaves like a call to pack_start(widget, FALSE, FALSE, 0)); the hexpand/vexpand/halign/valign properties described on the second page I linked can be used to change the parameters later.
Good luck!

Resources