phillipberndt / pqiv

Powerful image viewer with minimal UI
http://www.pberndt.com/Programme/Linux/pqiv/index.html
GNU General Public License v3.0
306 stars 44 forks source link

feature: image grid #82

Closed bearcatsandor closed 7 years ago

bearcatsandor commented 7 years ago

Please implement a feature wherein instead of a slideshow, a grid of images is shown. This would make visually searching for images within a directory or list very quick.

Pressing 'f' to follow images would be great as well. When 'f' is pressed the images could be overlaid with a alpha-number so that when they were typed the image would load singly.

phillipberndt commented 7 years ago

Please implement a feature wherein instead of a slideshow, a grid of images is shown. This would make visually searching for images within a directory or list very quick.

You've got the one from feh in mind, right? I thought about this a while ago and I am still undecided whether this'd make a good feature for pqiv. There already are external tools that compose images into a single image grid, and those tools can interface with pqiv (Though I don't have a variant of the command action yet that that gives the command a list of files rather than the currently viewed one). That is, you can simply run montage <files> - | pqiv - to achieve the desired effect.

To clarify, is your idea that you'd be able to click on a file to view it? How would a preview thumbnail in the "j" window work out for you?

Pressing 'f' to follow images would be great as well. When 'f' is pressed the images could be overlaid with a alpha-number so that when they were typed the image would load singly.

I'm afraid I don't understand this one. Could you elaborate please? (But see -F as well.)

noctuid commented 7 years ago

Sxiv also has this feature; it makes a bunch of thumbnails for the loaded images and places them in a grid. I never found it useful and didn't like that the thumbnail directory wasn't customizable; if I could, I would have disabled the feature or put the directory in tmpfs.

That said, it seems to me the intended use case here is navigation (specifically with the keyboard). If the 'f' feature was implemented, I'd actually find this useful myself. I don't know of any external tools that do anything like this. Are you familiar with link hinting in vim-like browsers or in plugins like pentadactyl/vimperator/cvim/vimium/etc.? There are a lot of vim and emacs plugins that use this functionality as well where they put overlays above text to be used for navigation to that text or to take some action on that text (e.g. see the gif here). I'm pretty sure that's what is being requested. You'd press a key and overlays with letters would appear on each image in the grid. You could then press those letters to navigate to the corresponding image.

bearcatsandor commented 7 years ago

Thanks noctuid, that gif is exactly the kind of thing i was thinking of. The purpose for this, at least in my mind is to make pqiv more friendly to keyboard-only people as well as being able to view a directory of images without having to open up a graphical file manager such as nautilus or dolphin to browse images quickly.

noctuid commented 7 years ago

How would a preview thumbnail in the "j" window work out for you?

I think this would be a nice improvement, especially if there was a way to press a key and then select from the list using letters (for example listed to the side). For example, the user could type as normal to narrow the list and then press something like Control-f to have typing choose the final candidate instead of narrowing further. The downside of this compared to an actual grid mode is that not as many pictures could be shown (since there is only one column). If there was a way to show multiple columns of thumbnails (maybe after the key to finalize the selection is pressed only thumbnails with letters next to them to appear), it could be extremely useful for quicker navigation I think.

This doesn't cover the case @bearcatsandor mentioned where you just want to view a bunch of pictures at once, but like you say, this could be possible using external tools. If there was a way to pass a command a list of files like you mention, that could solve this use case. I imagine it would be nice if there was a way to pass a group of the x closest files to a command and maybe if there was a way to pass x of the closest files in groups of y to a command so you could potentially create multiple images with montage.

phillipberndt commented 7 years ago

Ah, I get it. Thanks.

I see two ways to achieve this:

The second way brings much more flexibility, but would of course also mean that you'd have to do more setup to make this work. I think I'll implement the second idea in any case, let me know if you'd like to have the first one in addition.

In any way, this'll take some time. I'll be on vacation throughout most of March, and this feature isn't a particularly small one either.

noctuid commented 7 years ago

It seems like that could be useful for a lot of other cases, but I imagine that it would be pretty complicated for the user to implement this with external commands. It would require a lot of steps: merge the images, add the correct overlay for each image, send back the actions to bind all the keys, etc. There are also a lot of different cases it would have to handle. For example, maybe you wanted to generate a grid of images just to view and not to jump to, but then later you wanted to generate that same grid with overlays and jump to one of them. Also consider a case where you wanted to add images grids for all images or simulate scrolling be removing the top row and adding a new bottom row of images. Handling indexing in these cases would be a lot more complicated (or require pqiv to do extra work).

I think it makes a lot of sense to leave more complicated image manipulation and more complicated sorting/selection/filtration to external programs, but these are pretty simple with external commands/programs. Given some of the limitations and complications involved with continuously regenerating an image grid with external commands, I think it would make sense to implement the ability to display multiple images at once directly in pqiv.

As for the whole overlay thing, one of the nice things about the programs that do this is that they allow you to customize the keys used (sometimes with a customizable algorithm to use to determine the keys to use when there are more candidates than letters). This is already a pretty massive feature request, but if you do decide to implement this, it would be nice if there was a way to customize the keys like this, even if through an external command (e.g. pqiv sends the number of candidates to the command and the command returns a list of letters for each candidate).

phillipberndt commented 7 years ago

..but I imagine that it would be pretty complicated for the user to implement this with external commands

It's not that hard, actually. Montage can handle labeling already, and looping over all files, generating the montage arguments for the individual labels and one line of output for each key binding is a four-liner bash script. On the other hand, if I'd to this from within pqiv, I'd have to reproduce everything montage does. But I do of course see the benefits: It'd be ensured that the index can handle exactly the file formats that pqiv is able to handle, it could cache the thumbnails and could use a real GUI instead of a hack with one large image that is moved vertically to emulate scrolling.

I think it would make sense to implement the ability to display multiple images at once directly in pqiv

The issue with this is that this would rule out a major optimization that I have in place right now: Currently, there is exactly one current image, and the two adjacent images are loaded as well. That is, whenever the user requests a new image, pqiv knows in constant time which images to load and which images to unload. pqiv is really built around this assumption. Giving it up would mean lots of refactoring.

Anyway, I'll play around with the possibilities in April and let you know how it turns out :-)

noctuid commented 7 years ago

I know that montage can do labelling, and I agree that the most basic case would be pretty simple, but that wouldn't cover a lot of the basic functionality other image viewers have. Simulating those features (variable size grid, directional selection with a highlighted border around the "current" image, moving down a row and bringing in a new row, etc.) would significantly increase the complexity of the script. I imagine it would be problematic to try to generate a grid for every image with montage (especially if there were hundreds or thousands of them), so you probably would need to keep regenerating a smaller grid.

Either way this is a pretty complicated feature to implement. I'll be interested to see how things go. With or without an image grid, pqiv is still by far the best image viewer of its kind.

phillipberndt commented 7 years ago

The montage branch now has a minimal working first implementation of the feature (currently without the "follow" feature). Nothing is documented yet, most of the implementation details aren't configurable yet, and interrupting thumbnail generation might still crash pqiv, but it should be enough to have a first glance on it if you'd like. The montage window is bound to Ctrl+j by default, movement within the image grid to the cursor keys, and the mode may be left again by pressing Return (which will load the selected image). I'd appreciate suggestions and feedback on whether I got something fundamentally wrong :-)

Update 2017-04-18: The current version of the branch has montage mode bound to m. It's quite stable already, and I've documented the settings. f mode is still missing.

noctuid commented 7 years ago

I think it looks great so far! Here are some thoughts about the current functionality:

phillipberndt commented 7 years ago

It would be nice to be able to control the size and color of the border of the selected image.

My plan is to integrate this with #83.

Once I switch to montage mode, only the first one or two thumbnails are generated until I either press one of the navigation actions or take some action with my window manager (this only happens if the window is tiled and not floating).

This should be fixed with one of the latest commits.

(Edit: Ah, that might be the case. I haven't tested this with different WMs yet, will do before merging.)

Thumbnail generation without caching seems fast enough for me, but it would be nice to have a user-settable number of thumbnails generated before and after the currently visible ones.

I've been thinking about this as well. The downside is that having this would mean that pqiv would have a high CPU usage until all thumbnails have been generated in the background, each time it is started.. that's why I didn't include this by default. --persist-thumbnails is a small improvement (which shares the thumbnail cache with all other thumbnailers which comply with the Thumbnail Managing Standard) already. Having an option to pregenerate the thumbnails in the background is a good idea though, I agree.

Is it possible to bind keys in the montage context from the config file yet?

Yes, I've modified the key binding syntax for this (and didn't document that yet, sorry). Run pqiv --show-bindings, the syntax should be obvious.

Another thing I'm uncertain about is whether pqiv should unload thumbnails after some time. It currently persists a small thumbnail for every image in RAM.

noctuid commented 7 years ago

This should be fixed with one of the latest commits.

I'm still having the same problem after updating. I don't know if you saw my edit since you replied right after it, but I'm guessing this problem might be specific to my window manger (bspwm; like with #48).

The downside is that having this would mean that pqiv would have a high CPU usage until all thumbnails have been generated in the background, each time it is started.

I'd use a small enough number that it wouldn't matter for me. Sxiv (at least last time I used it) actually generated thumbnails for all loaded images immediately upon entering its thumbnail mode (which is quite annoying in large directories). Pqiv's behavior is a much better default I think.

Run pqiv --show-bindings, the syntax should be obvious.

Thanks! One minor thing that might be nice would be to have a montage version of goto_file_byindex(). { montage_mode_return_cancel(); goto_file_byindex(x); montage_mode_enter(); } works fine for me though.

Another thing I'm uncertain about is whether pqiv should unload thumbnails after some time.

I didn't notice any significant increase in ram usage after going through quite a large directory of images in montage mode. I don't think this would ever be an issue for me, but I guess this might be useful for people using --low-memory.

phillipberndt commented 7 years ago

I'm still having the same problem after updating. I don't know if you saw my edit since you replied right after it, but I'm guessing this problem might be specific to my window manger (bspwm; like with #48).

I did and edited my answer :-) I'll test with bspwm asap! (Edit: Works for me with bspwm master, both if running pqiv as a tiled and as a floating window :roll_eyes: I'll see if I can work out how to reproduce this tomorrow.)

but I guess this might be useful for people using --low-memory

Good point, thanks!

noctuid commented 7 years ago

In that case, the problem may not have to do with bspwm. I tried with the latest release of bspwm and on master without success.

I've been trying this out more, and I really like it. I repurposed my zoom keys to cycle between different thumbnail sizes in montage mode; very nice. I also have a couple more comments now:

phillipberndt commented 7 years ago

Thanks! One minor thing that might be nice would be to have a montage version of goto_file_byindex().

I'd like to have your opinion on that: Would you find it more natural to use goto_file_byindex in montage mode to focus a particular file, or to add a separate command for that? If it should be a separate command, would it be better if goto_file_byindex returned to normal mode, if it switched images in the background (such that canceling montage mode goes to that file) or if it'd simply not work?

noctuid commented 7 years ago

I think it would be more natural for goto_file_byindex to work for montage mode as well (without exiting). As an alternative possibility, I think it would make more sense for it to go to the image and return to normal mode instead of doing nothing. Either way, I think it might make sense to keep the behavior ofgoto_file_byindex, goto_file_relative, and goto_earlier_file in montage mode consistent.

phillipberndt commented 7 years ago
noctuid commented 7 years ago

Great, thumbnails now are stored for all options as expected for me. I don't know if it matters, but I don't have the issue where only the first couple of images are shown when I use thumbnail caching and there are thumbnails for the first images shown in montage mode.

goto_file_byindex works correctly for me now, but goto_file_relative is behaving the same as goto_file_byindex in montage mode.

If I change between thumbnail sizes that aren't 256x256 or 128x128, they will start to be mixed (e.g. larger or smaller thumbnails appear for some images until I select them and then they become the correct size). It would be nice if thumbnails in the x-pqiv or user-specified directory were stored according to their size too.

bearcatsandor commented 7 years ago

Is it a reasonable suggestion to give people a masonry layout instead of a grid so that elements don't have to be the same ratios and could just be scaled down? Would thumbnails even be needed in that case?

phillipberndt commented 7 years ago

Is it a reasonable suggestion to give people a masonry layout instead of a grid so that elements don't have to be the same ratios and could just be scaled down? Would thumbnails even be needed in that case?

That's much more complicated than the current implementation. You need to break the image order and group images into sets of equal height to achieve a masonry layout (probably similarly to how a Huffman coding is created, by grouping images until all groups roughly have the same total height), and then draw them out-of-order. This layout would have to be stored away rather than generated on the fly, because navigation in a layout where there can be multiple images to either side of an image otherwise becomes computationally expensive. But pqiv supports loading images in a separate thread and/or as they appear while still maintaining their order, that is, new images can appear before the current image. This means that rather than appending new images to the layout, pqiv would have to regenerate it each time a new image appears, or accept that images are drawn completely out of order.

(By this I don't want to say that I won't consider this, but given the size of this feature I tend to think of this as another, separate feature request.)

bearcatsandor commented 7 years ago

I agree. I was just thinking as a webdeveloper. Furthermore, it works well in a browser where most people would use a mouse. As a keyboard based application it might be confusing to navigate from picture to picture.

Thank you for considering it though.

phillipberndt commented 7 years ago

goto_file_byindex works correctly for me now, but goto_file_relative is behaving the same as goto_file_byindex in montage mode.

This should be fixed now.

If I change between thumbnail sizes that aren't 256x256 or 128x128, they will start to be mixed (e.g. larger or smaller thumbnails appear for some images until I select them and then they become the correct size).

Should be fixed as well.

It would be nice if thumbnails in the x-pqiv or user-specified directory were stored according to their size too.

Right. I've changed the directory structure slightly, pqiv now uses $FOLDER/x-pqiv/<width>x<height>/, and it also uses that structure if a user-specified directory is being used.

..it works well in a browser where most people would use a mouse

This reminds me that I should add at least rudimentary mouse support ;-)

I'll work on the text overlays for the follow feature next (configuring "follow" key bindings is already possible, I've added montage_mode_set_shift_x and montage_mode_set_shift_y.)

noctuid commented 7 years ago

I'm now having some issues where pqiv will sometimes crash if I change the thumbnail size in montage mode (e.g. from 256x256 to 320x320). A few images are also are staying the same size when changing thumbnail sizes while still in montage mode (and now they don't reset).

phillipberndt commented 7 years ago

That should be fixed now.

noctuid commented 7 years ago

Great, thanks!

phillipberndt commented 7 years ago

FYI, I've renamed --persist-thumbnails to --thumbnail-persistence: Optional arguments don't really work with Glib and pqiv, because it interprets "something" in "--option_name something" as an argument even if it's an image to be viewed, and --persist-thumbnails had a ring to it as if it was allowed to use this parameter without an argument.

phillipberndt commented 7 years ago

You can use "follow mode" now, though I have to admit that configuration is a bit painful. Basically, I have introduced a new action, montage_mode_follow, which will enable a switch such that upon drawing the thumbnail overview, pqiv will lookup all key bindings to check whether one of them will navigate to that thumbnail (without doing fancy stuff such as following send_keys though). If it finds one, it will overlay the associated key combination onto the thumbnail. Since you'd typically need some time to read which keys to press, I've also added set_keyboard_timeout, which you can use to alter the timeout after which pqiv assumes that you gave up on entering a key combination. Here's how you can use it to implement follow mode using your configuration file:

[keybindings]
@MONTAGE {
    g { montage_mode_follow(1); set_keyboard_timeout(5); send_keys(#); }

    <numbersign> { montage_mode_follow(0); set_keyboard_timeout(0.5); }

    <numbersign>q { montage_mode_follow(0); set_keyboard_timeout(0.5); }
    <numbersign>qa { montage_mode_set_shift_x(0); montage_mode_set_shift_y(0); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>qs { montage_mode_set_shift_x(0); montage_mode_set_shift_y(1); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>qd { montage_mode_set_shift_x(0); montage_mode_set_shift_y(2); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>qf { montage_mode_set_shift_x(0); montage_mode_set_shift_y(3); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>qg { montage_mode_set_shift_x(0); montage_mode_set_shift_y(4); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>qh { montage_mode_set_shift_x(0); montage_mode_set_shift_y(5); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>qj { montage_mode_set_shift_x(0); montage_mode_set_shift_y(6); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>qk { montage_mode_set_shift_x(0); montage_mode_set_shift_y(7); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>ql { montage_mode_set_shift_x(0); montage_mode_set_shift_y(8); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>qy { montage_mode_set_shift_x(0); montage_mode_set_shift_y(9); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>qx { montage_mode_set_shift_x(0); montage_mode_set_shift_y(10); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>qc { montage_mode_set_shift_x(0); montage_mode_set_shift_y(11); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>qv { montage_mode_set_shift_x(0); montage_mode_set_shift_y(12); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>qb { montage_mode_set_shift_x(0); montage_mode_set_shift_y(13); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>qn { montage_mode_set_shift_x(0); montage_mode_set_shift_y(14); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>qm { montage_mode_set_shift_x(0); montage_mode_set_shift_y(15); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>a { montage_mode_follow(0); set_keyboard_timeout(0.5); }
    <numbersign>aa { montage_mode_set_shift_x(1); montage_mode_set_shift_y(0); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>as { montage_mode_set_shift_x(1); montage_mode_set_shift_y(1); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>ad { montage_mode_set_shift_x(1); montage_mode_set_shift_y(2); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>af { montage_mode_set_shift_x(1); montage_mode_set_shift_y(3); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>ag { montage_mode_set_shift_x(1); montage_mode_set_shift_y(4); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>ah { montage_mode_set_shift_x(1); montage_mode_set_shift_y(5); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>aj { montage_mode_set_shift_x(1); montage_mode_set_shift_y(6); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>ak { montage_mode_set_shift_x(1); montage_mode_set_shift_y(7); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>al { montage_mode_set_shift_x(1); montage_mode_set_shift_y(8); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>ay { montage_mode_set_shift_x(1); montage_mode_set_shift_y(9); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>ax { montage_mode_set_shift_x(1); montage_mode_set_shift_y(10); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>ac { montage_mode_set_shift_x(1); montage_mode_set_shift_y(11); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>av { montage_mode_set_shift_x(1); montage_mode_set_shift_y(12); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>ab { montage_mode_set_shift_x(1); montage_mode_set_shift_y(13); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>an { montage_mode_set_shift_x(1); montage_mode_set_shift_y(14); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>am { montage_mode_set_shift_x(1); montage_mode_set_shift_y(15); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>y { montage_mode_follow(0); set_keyboard_timeout(0.5); }
    <numbersign>ya { montage_mode_set_shift_x(2); montage_mode_set_shift_y(0); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>ys { montage_mode_set_shift_x(2); montage_mode_set_shift_y(1); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>yd { montage_mode_set_shift_x(2); montage_mode_set_shift_y(2); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>yf { montage_mode_set_shift_x(2); montage_mode_set_shift_y(3); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>yg { montage_mode_set_shift_x(2); montage_mode_set_shift_y(4); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>yh { montage_mode_set_shift_x(2); montage_mode_set_shift_y(5); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>yj { montage_mode_set_shift_x(2); montage_mode_set_shift_y(6); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>yk { montage_mode_set_shift_x(2); montage_mode_set_shift_y(7); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>yl { montage_mode_set_shift_x(2); montage_mode_set_shift_y(8); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>yy { montage_mode_set_shift_x(2); montage_mode_set_shift_y(9); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>yx { montage_mode_set_shift_x(2); montage_mode_set_shift_y(10); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>yc { montage_mode_set_shift_x(2); montage_mode_set_shift_y(11); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>yv { montage_mode_set_shift_x(2); montage_mode_set_shift_y(12); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>yb { montage_mode_set_shift_x(2); montage_mode_set_shift_y(13); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>yn { montage_mode_set_shift_x(2); montage_mode_set_shift_y(14); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>ym { montage_mode_set_shift_x(2); montage_mode_set_shift_y(15); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>w { montage_mode_follow(0); set_keyboard_timeout(0.5); }
    <numbersign>wa { montage_mode_set_shift_x(3); montage_mode_set_shift_y(0); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>ws { montage_mode_set_shift_x(3); montage_mode_set_shift_y(1); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>wd { montage_mode_set_shift_x(3); montage_mode_set_shift_y(2); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>wf { montage_mode_set_shift_x(3); montage_mode_set_shift_y(3); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>wg { montage_mode_set_shift_x(3); montage_mode_set_shift_y(4); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>wh { montage_mode_set_shift_x(3); montage_mode_set_shift_y(5); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>wj { montage_mode_set_shift_x(3); montage_mode_set_shift_y(6); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>wk { montage_mode_set_shift_x(3); montage_mode_set_shift_y(7); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>wl { montage_mode_set_shift_x(3); montage_mode_set_shift_y(8); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>wy { montage_mode_set_shift_x(3); montage_mode_set_shift_y(9); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>wx { montage_mode_set_shift_x(3); montage_mode_set_shift_y(10); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>wc { montage_mode_set_shift_x(3); montage_mode_set_shift_y(11); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>wv { montage_mode_set_shift_x(3); montage_mode_set_shift_y(12); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>wb { montage_mode_set_shift_x(3); montage_mode_set_shift_y(13); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>wn { montage_mode_set_shift_x(3); montage_mode_set_shift_y(14); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>wm { montage_mode_set_shift_x(3); montage_mode_set_shift_y(15); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>s { montage_mode_follow(0); set_keyboard_timeout(0.5); }
    <numbersign>sa { montage_mode_set_shift_x(4); montage_mode_set_shift_y(0); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>ss { montage_mode_set_shift_x(4); montage_mode_set_shift_y(1); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>sd { montage_mode_set_shift_x(4); montage_mode_set_shift_y(2); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>sf { montage_mode_set_shift_x(4); montage_mode_set_shift_y(3); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>sg { montage_mode_set_shift_x(4); montage_mode_set_shift_y(4); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>sh { montage_mode_set_shift_x(4); montage_mode_set_shift_y(5); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>sj { montage_mode_set_shift_x(4); montage_mode_set_shift_y(6); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>sk { montage_mode_set_shift_x(4); montage_mode_set_shift_y(7); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>sl { montage_mode_set_shift_x(4); montage_mode_set_shift_y(8); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>sy { montage_mode_set_shift_x(4); montage_mode_set_shift_y(9); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>sx { montage_mode_set_shift_x(4); montage_mode_set_shift_y(10); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>sc { montage_mode_set_shift_x(4); montage_mode_set_shift_y(11); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>sv { montage_mode_set_shift_x(4); montage_mode_set_shift_y(12); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>sb { montage_mode_set_shift_x(4); montage_mode_set_shift_y(13); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>sn { montage_mode_set_shift_x(4); montage_mode_set_shift_y(14); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>sm { montage_mode_set_shift_x(4); montage_mode_set_shift_y(15); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>x { montage_mode_follow(0); set_keyboard_timeout(0.5); }
    <numbersign>xa { montage_mode_set_shift_x(5); montage_mode_set_shift_y(0); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>xs { montage_mode_set_shift_x(5); montage_mode_set_shift_y(1); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>xd { montage_mode_set_shift_x(5); montage_mode_set_shift_y(2); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>xf { montage_mode_set_shift_x(5); montage_mode_set_shift_y(3); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>xg { montage_mode_set_shift_x(5); montage_mode_set_shift_y(4); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>xh { montage_mode_set_shift_x(5); montage_mode_set_shift_y(5); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>xj { montage_mode_set_shift_x(5); montage_mode_set_shift_y(6); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>xk { montage_mode_set_shift_x(5); montage_mode_set_shift_y(7); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>xl { montage_mode_set_shift_x(5); montage_mode_set_shift_y(8); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>xy { montage_mode_set_shift_x(5); montage_mode_set_shift_y(9); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>xx { montage_mode_set_shift_x(5); montage_mode_set_shift_y(10); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>xc { montage_mode_set_shift_x(5); montage_mode_set_shift_y(11); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>xv { montage_mode_set_shift_x(5); montage_mode_set_shift_y(12); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>xb { montage_mode_set_shift_x(5); montage_mode_set_shift_y(13); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>xn { montage_mode_set_shift_x(5); montage_mode_set_shift_y(14); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>xm { montage_mode_set_shift_x(5); montage_mode_set_shift_y(15); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>e { montage_mode_follow(0); set_keyboard_timeout(0.5); }
    <numbersign>ea { montage_mode_set_shift_x(6); montage_mode_set_shift_y(0); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>es { montage_mode_set_shift_x(6); montage_mode_set_shift_y(1); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>ed { montage_mode_set_shift_x(6); montage_mode_set_shift_y(2); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>ef { montage_mode_set_shift_x(6); montage_mode_set_shift_y(3); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>eg { montage_mode_set_shift_x(6); montage_mode_set_shift_y(4); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>eh { montage_mode_set_shift_x(6); montage_mode_set_shift_y(5); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>ej { montage_mode_set_shift_x(6); montage_mode_set_shift_y(6); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>ek { montage_mode_set_shift_x(6); montage_mode_set_shift_y(7); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>el { montage_mode_set_shift_x(6); montage_mode_set_shift_y(8); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>ey { montage_mode_set_shift_x(6); montage_mode_set_shift_y(9); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>ex { montage_mode_set_shift_x(6); montage_mode_set_shift_y(10); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>ec { montage_mode_set_shift_x(6); montage_mode_set_shift_y(11); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>ev { montage_mode_set_shift_x(6); montage_mode_set_shift_y(12); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>eb { montage_mode_set_shift_x(6); montage_mode_set_shift_y(13); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>en { montage_mode_set_shift_x(6); montage_mode_set_shift_y(14); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>em { montage_mode_set_shift_x(6); montage_mode_set_shift_y(15); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>d { montage_mode_follow(0); set_keyboard_timeout(0.5); }
    <numbersign>da { montage_mode_set_shift_x(7); montage_mode_set_shift_y(0); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>ds { montage_mode_set_shift_x(7); montage_mode_set_shift_y(1); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>dd { montage_mode_set_shift_x(7); montage_mode_set_shift_y(2); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>df { montage_mode_set_shift_x(7); montage_mode_set_shift_y(3); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>dg { montage_mode_set_shift_x(7); montage_mode_set_shift_y(4); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>dh { montage_mode_set_shift_x(7); montage_mode_set_shift_y(5); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>dj { montage_mode_set_shift_x(7); montage_mode_set_shift_y(6); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>dk { montage_mode_set_shift_x(7); montage_mode_set_shift_y(7); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>dl { montage_mode_set_shift_x(7); montage_mode_set_shift_y(8); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>dy { montage_mode_set_shift_x(7); montage_mode_set_shift_y(9); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>dx { montage_mode_set_shift_x(7); montage_mode_set_shift_y(10); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>dc { montage_mode_set_shift_x(7); montage_mode_set_shift_y(11); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>dv { montage_mode_set_shift_x(7); montage_mode_set_shift_y(12); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>db { montage_mode_set_shift_x(7); montage_mode_set_shift_y(13); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>dn { montage_mode_set_shift_x(7); montage_mode_set_shift_y(14); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>dm { montage_mode_set_shift_x(7); montage_mode_set_shift_y(15); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>c { montage_mode_follow(0); set_keyboard_timeout(0.5); }
    <numbersign>ca { montage_mode_set_shift_x(8); montage_mode_set_shift_y(0); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>cs { montage_mode_set_shift_x(8); montage_mode_set_shift_y(1); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>cd { montage_mode_set_shift_x(8); montage_mode_set_shift_y(2); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>cf { montage_mode_set_shift_x(8); montage_mode_set_shift_y(3); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>cg { montage_mode_set_shift_x(8); montage_mode_set_shift_y(4); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>ch { montage_mode_set_shift_x(8); montage_mode_set_shift_y(5); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>cj { montage_mode_set_shift_x(8); montage_mode_set_shift_y(6); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>ck { montage_mode_set_shift_x(8); montage_mode_set_shift_y(7); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>cl { montage_mode_set_shift_x(8); montage_mode_set_shift_y(8); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>cy { montage_mode_set_shift_x(8); montage_mode_set_shift_y(9); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>cx { montage_mode_set_shift_x(8); montage_mode_set_shift_y(10); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>cc { montage_mode_set_shift_x(8); montage_mode_set_shift_y(11); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>cv { montage_mode_set_shift_x(8); montage_mode_set_shift_y(12); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>cb { montage_mode_set_shift_x(8); montage_mode_set_shift_y(13); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>cn { montage_mode_set_shift_x(8); montage_mode_set_shift_y(14); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>cm { montage_mode_set_shift_x(8); montage_mode_set_shift_y(15); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>r { montage_mode_follow(0); set_keyboard_timeout(0.5); }
    <numbersign>ra { montage_mode_set_shift_x(9); montage_mode_set_shift_y(0); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>rs { montage_mode_set_shift_x(9); montage_mode_set_shift_y(1); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>rd { montage_mode_set_shift_x(9); montage_mode_set_shift_y(2); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>rf { montage_mode_set_shift_x(9); montage_mode_set_shift_y(3); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>rg { montage_mode_set_shift_x(9); montage_mode_set_shift_y(4); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>rh { montage_mode_set_shift_x(9); montage_mode_set_shift_y(5); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>rj { montage_mode_set_shift_x(9); montage_mode_set_shift_y(6); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>rk { montage_mode_set_shift_x(9); montage_mode_set_shift_y(7); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>rl { montage_mode_set_shift_x(9); montage_mode_set_shift_y(8); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>ry { montage_mode_set_shift_x(9); montage_mode_set_shift_y(9); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>rx { montage_mode_set_shift_x(9); montage_mode_set_shift_y(10); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>rc { montage_mode_set_shift_x(9); montage_mode_set_shift_y(11); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>rv { montage_mode_set_shift_x(9); montage_mode_set_shift_y(12); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>rb { montage_mode_set_shift_x(9); montage_mode_set_shift_y(13); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>rn { montage_mode_set_shift_x(9); montage_mode_set_shift_y(14); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>rm { montage_mode_set_shift_x(9); montage_mode_set_shift_y(15); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>f { montage_mode_follow(0); set_keyboard_timeout(0.5); }
    <numbersign>fa { montage_mode_set_shift_x(10); montage_mode_set_shift_y(0); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>fs { montage_mode_set_shift_x(10); montage_mode_set_shift_y(1); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>fd { montage_mode_set_shift_x(10); montage_mode_set_shift_y(2); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>ff { montage_mode_set_shift_x(10); montage_mode_set_shift_y(3); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>fg { montage_mode_set_shift_x(10); montage_mode_set_shift_y(4); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>fh { montage_mode_set_shift_x(10); montage_mode_set_shift_y(5); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>fj { montage_mode_set_shift_x(10); montage_mode_set_shift_y(6); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>fk { montage_mode_set_shift_x(10); montage_mode_set_shift_y(7); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>fl { montage_mode_set_shift_x(10); montage_mode_set_shift_y(8); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>fy { montage_mode_set_shift_x(10); montage_mode_set_shift_y(9); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>fx { montage_mode_set_shift_x(10); montage_mode_set_shift_y(10); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>fc { montage_mode_set_shift_x(10); montage_mode_set_shift_y(11); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>fv { montage_mode_set_shift_x(10); montage_mode_set_shift_y(12); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>fb { montage_mode_set_shift_x(10); montage_mode_set_shift_y(13); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>fn { montage_mode_set_shift_x(10); montage_mode_set_shift_y(14); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>fm { montage_mode_set_shift_x(10); montage_mode_set_shift_y(15); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>v { montage_mode_follow(0); set_keyboard_timeout(0.5); }
    <numbersign>va { montage_mode_set_shift_x(11); montage_mode_set_shift_y(0); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>vs { montage_mode_set_shift_x(11); montage_mode_set_shift_y(1); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>vd { montage_mode_set_shift_x(11); montage_mode_set_shift_y(2); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>vf { montage_mode_set_shift_x(11); montage_mode_set_shift_y(3); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>vg { montage_mode_set_shift_x(11); montage_mode_set_shift_y(4); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>vh { montage_mode_set_shift_x(11); montage_mode_set_shift_y(5); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>vj { montage_mode_set_shift_x(11); montage_mode_set_shift_y(6); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>vk { montage_mode_set_shift_x(11); montage_mode_set_shift_y(7); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>vl { montage_mode_set_shift_x(11); montage_mode_set_shift_y(8); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>vy { montage_mode_set_shift_x(11); montage_mode_set_shift_y(9); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>vx { montage_mode_set_shift_x(11); montage_mode_set_shift_y(10); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>vc { montage_mode_set_shift_x(11); montage_mode_set_shift_y(11); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>vv { montage_mode_set_shift_x(11); montage_mode_set_shift_y(12); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>vb { montage_mode_set_shift_x(11); montage_mode_set_shift_y(13); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>vn { montage_mode_set_shift_x(11); montage_mode_set_shift_y(14); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>vm { montage_mode_set_shift_x(11); montage_mode_set_shift_y(15); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>t { montage_mode_follow(0); set_keyboard_timeout(0.5); }
    <numbersign>ta { montage_mode_set_shift_x(12); montage_mode_set_shift_y(0); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>ts { montage_mode_set_shift_x(12); montage_mode_set_shift_y(1); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>td { montage_mode_set_shift_x(12); montage_mode_set_shift_y(2); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>tf { montage_mode_set_shift_x(12); montage_mode_set_shift_y(3); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>tg { montage_mode_set_shift_x(12); montage_mode_set_shift_y(4); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>th { montage_mode_set_shift_x(12); montage_mode_set_shift_y(5); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>tj { montage_mode_set_shift_x(12); montage_mode_set_shift_y(6); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>tk { montage_mode_set_shift_x(12); montage_mode_set_shift_y(7); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>tl { montage_mode_set_shift_x(12); montage_mode_set_shift_y(8); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>ty { montage_mode_set_shift_x(12); montage_mode_set_shift_y(9); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>tx { montage_mode_set_shift_x(12); montage_mode_set_shift_y(10); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>tc { montage_mode_set_shift_x(12); montage_mode_set_shift_y(11); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>tv { montage_mode_set_shift_x(12); montage_mode_set_shift_y(12); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>tb { montage_mode_set_shift_x(12); montage_mode_set_shift_y(13); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>tn { montage_mode_set_shift_x(12); montage_mode_set_shift_y(14); montage_mode_follow(0); set_keyboard_timeout(0.5);}
    <numbersign>tm { montage_mode_set_shift_x(12); montage_mode_set_shift_y(15); montage_mode_follow(0); set_keyboard_timeout(0.5);}

    # etc. You'll obviously want to generate this using a script for a larger matrix ;-) This one
    # is large enough for 1900x1080 with 150x150px thumbs.
}

(I do realize that you could write this in a much shorter form if I would follow send_keys, because you could then do the navigation in two sweeps, but then I'd have to detect infinite loops etc. and I really want to keep this simple.)

Questions:

I think that there's still a race somewhere in the spectre backend at least, but I haven't found a reliable way to reproduce the issue yet. Still, I'll probably merge this into master soon to have more testers.

noctuid commented 7 years ago

One of the primary features of this overlay functionality is that the overlay characters are dynamically generated based on the number candidates and the keys the user provides. So if the user provides 12 keys, and there are 10 candidates, each candidate will be selectable with a single key. Only once there are >12 candidates will some or all of them require multiple keys. Do you see any way to implement this sort of functionality directly in pqiv without too much complexity? If you think it's a bad idea, would you consider allowing for the user to do this with a script? For example, a command could be provided to pass the current number of thumbnails in a row/column to a script that would output the correct pqiv keybindings. This command could then be called before montage_mode_follow by the follow keybinding.

phillipberndt commented 7 years ago

The reason I didn't implement that yet is that I can't decide on the best way to make that configurable. Should there be one set of keys, such that if there are more images than keys pqiv can simply use that set to form a base n representation of the image id? Or should the algorithm be more intelligent and try to make the first character be the row and the second the column? Should there be multiple sets of keys, depending on the total count, such that if you only have two images totally different keys are used than if you have e.g. 30? And so forth. I'm open for suggestions.

(Passing the task on to an external script is of course a plausible last-resort solution, but this would mean that follow mode won't work by default..)

noctuid commented 7 years ago

All the implementations I've used allow the user to specify one list of keys (e.g. "asdfghkl"). None of them actually dealt with grids, but I don't think trying to match letters with rows/columns is a good idea. It wouldn't work correctly if the user didn't have enough keys for how many rows/columns they had, and I don't think it would give too much benefit since determining the right keys to press based on the row/column wouldn't be as fast as just looking at the overlay. This also wouldn't allow for some images to be reachable with a single key.

The way avy does it is to prioritize early keys and candidates. The assumption that earlier candidates are better is not necessarily true in a lot of cases, but it seems that most systems work this way (e.g. pentadactyl prioritizes earlier urls on the page). I think it's a sensible and common enough behavior for cases where none of the candidates are "better" or more likely to be selected. For montage mode, this is pretty much the case except the user may be less likely to want to follow the current image or the ones right next to it. I don't know if it makes sense to make special exceptions for these cases though.

The algorithm avy uses is pretty simple. Once the threshold is crossed when more than 2 keys must be used for some candidates, it will keep the keys towards the beginning of the list as the single keys and use the keys at the end as prefixes. Some examples:

keys: asdf 4 candidates: a, s, d, f 8 candidates: a, s, da, ds, fa, fs, fd, ff 17 candidates: aa, as, ad, af, sa, ss, sd, sf, da, ds, dd, df, fa, fs, fd, ffa, ffs

Pentadactyl does things the other way and will use letters at the beginning of the list as prefixes first. Either scheme seems sensible to me for montage mode.

As for the script, if it comes to that, my idea was that it would be optional (the user could still set the keys in their config, but it would also be possible to use the script to regenerate the keybindings for the current thumbnail layout).

phillipberndt commented 7 years ago

If it's only one set of keys then I think this is something which is actually quite easy to add to the current implementation :-)

Another question: Since in pqiv there's no obvious choice for "early" / "good" candidates, is it still important to optimize for a short average key combination length, or would it be reasonable to use the same (minimal) amount of keys for all images on the screen? An example with an extreme case is 4 keys and 16 (selectable) images - you'll need to assign a combination of 3 keys to two images at least (because 16 = 4²) - but it'd be simpler to use 3 keys for all images in this case. Would that work, or would it again defeat the purpose?

noctuid commented 7 years ago

I'd prefer that it use as few keys as possible, especially in cases where only a couple of images would need the longer number of keys. Even though no images are "better", there is at least some chance that the user won't need to use the longer key length, and if only a few images have the longer key length, the chance is high.

phillipberndt commented 7 years ago

For now, I've started with a version that assigns the same amount of keys to each thumbnail. It's bound to g by default, and you can change the keys by reassigning g - it's bound to an action montage_mode_follow() that takes the characters to use as an argument.

The next step will be to minimize the number of required characters per thumbnail.

noctuid commented 7 years ago

Very cool; it is working as expected for me. As another request, would it be possible to have a version that exits montage mode or to have montage_mode_return_proceed work after montage_mode_follow in the same keybinding?

phillipberndt commented 7 years ago

As another request, would it be possible to [..] have montage_mode_return_proceed work after montage_mode_follow in the same keybinding?

That'll work now, unless you abort follow mode (by pressing an invalid key).

I've also implemented a more intelligent algorithm for selection of key sequences. An interesting problem, actually. I had expected that you'd have to come up with an elaborate base m (m as in m keys) prefix code, but actually, it's really only about base m representation of numbers. I believe that the version I now have is optimal.

With this, the implementation should be feature complete :-)

noctuid commented 7 years ago

The hinting is great; this is a very cool feature. Thanks for the hinting and the rest of the montage mode features!

As for the preloading, it does not seem to be working for me (images take the same amount of time to load when scrolling down instead of displaying immediately). Also does the number refer to the total number of preloaded images (e.g. would 10 be 5 on either side of the currently displayed ones)?

I have a few other minor issues/suggestions. When I chain montage_mode_follow and montage_mode_return_proceed, the info box flashes upon exiting montage mode, which is a little distracting. Also, goto_file_relative in montage mode can cause pqiv to crash for me. Finally, it might be nice if montage mode could retain the shuffled image order when shuffle mode is being used.

phillipberndt commented 7 years ago

The hinting is great; this is a very cool feature. Thanks for the hinting and the rest of the montage mode features!

I'm pretty enthusiastic about it myself. @bearcatsandor, thanks for the suggestion.

As for the preloading, it does not seem to be working for me (images take the same amount of time to load when scrolling down instead of displaying immediately). Also does the number refer to the total number of preloaded images (e.g. would 10 be 5 on either side of the currently displayed ones)?

Ah, that's not how this works. --thumbnail-preload=10 means that if you display an image (not in montage mode), pqiv prerenders 10 thumbnails to either side of the current image for quicker transition into montage mode. But that's good thinking there, I should extend the feature to load images in montage mode as well. (Already done & pushed while writing this comment.)

When I chain montage_mode_follow and montage_mode_return_proceed, the info box flashes upon exiting montage mode, which is a little distracting.

That probably was the "The image is still loading" text. I agree that it shouldn't be visible if using -i - I just removed it.

Also, goto_file_relative in montage mode can cause pqiv to crash for me.

Thanks, I'll test all the actions again before pulling this.

(This particular one is fixed: The action acted in montage mode and at the same time actually changed the image in the background. That was not intended, the idea was to leave the current image untouched, of course.)

Finally, it might be nice if montage mode could retain the shuffled image order when shuffle mode is being used.

Since pqiv maintains the true image order while in shuffle mode (shuffle mode can be temporarily disabled and re-enabled and the counters show the real image number) I can see use cases for both orderings. I'll leave things as they are for now and will add this as another feature later.

noctuid commented 7 years ago

goto_file_relative is no longer causing crashes for me, but it behaving as goto_file_byindex again.

I should extend the feature to load images in montage mode as well. (Already done & pushed while writing this comment.)

Thank you; it's working great.

That probably was the "The image is still loading" text. I agree that it shouldn't be visible if using -i - I just removed it.

I've noticed that the previous image will quickly flash too (the one loaded before entering montage mode). It would be nice if it was the background color until the image loaded instead.

phillipberndt commented 7 years ago

goto_file_relative is no longer causing crashes for me, but it behaving as goto_file_byindex again.

goto_file_relative means relative to the currently loaded image, which is a different thing than the image currently selected in montage mode. If you switch to montage mode from the second image, then goto_file_relative(1) will select the third image. To move the cursor relative to the selection, you can use montage_mode_shift_x(..) - it'll wrap around edges. (You cannot use it to go to the first image from the last one and vice versa though, let me know if you need that.)

I've noticed that the previous image will quickly flash too (the one loaded before entering montage mode). It would be nice if it was the background color until the image loaded instead.

Right. Fixed.

noctuid commented 7 years ago

goto_file_relative means relative to the currently loaded image, which is a different thing than the image currently selected in montage mode.

Ah thanks for the explanation. I was confused because of the behavior before, but your previous explanation about what was wrong with it is clear now.

You cannot use it to go to the first image from the last one and vice versa though, let me know if you need that.

Wrapping was why I was using goto_file_relative instead. It might be nice if montage_mode_shift_x behaved according to end-of-files-action or if there was another option if it doesn't make sense to reuse end-of-files-action.

phillipberndt commented 7 years ago

Wrapping was why I was using goto_file_relative instead. It might be nice if montage_mode_shift_x behaved according to end-of-files-action or if there was another option if it doesn't make sense to reuse end-of-files-action.

I feared so :) I've changed how montage mode navigation works, and in particular added an action montage_mode_set_wrap_mode which allows you to wrap around the list. This isn't fully tested yet, but it's stable enough for testing, I guess.

noctuid commented 7 years ago

Thanks. The wrapping works fine for me so far now, but with the new commit, everything turns black for me in montage mode if I make pqiv fullscreen (regardless of the new setting's value).

phillipberndt commented 7 years ago

I'm having trouble reproducing this with bspwm master. Could you check if this is related to one of your configuration options? Does this happen only initially upon switching to fullscreen, or does the screen stay black if you try to move the cursor / take some other action?

noctuid commented 7 years ago

EDIT: I think maybe I am misremembering and an extra action was required after entering fullscreen to turn things black (I tried with the original image set I was using). I've updated my comment to reflect this.

It may be that it's the number of images that matters. If there are no images on the last row, any navigation action will turn pqiv black. If there is one image on the last row, things will work as expected, but if I enter fullscreen and an extra row becomes available, navigation actions will turn the screen black. I believe this is what was happening before.

I also found another issue when there are more rows than can be shown on screen. In this case, if the last row is completely full, I cannot navigate to it (pqiv will skip around it). If I start on an image in this row, it will be selected, but any navigation will navigate away from it.

phillipberndt commented 7 years ago

Great, thanks a lot. The issue appeared whenever there were too few images to fill one page. That's fixed now, and I've fixed the unusable-last-row issue as well.

phillipberndt commented 7 years ago

I've merged what we have so far into master, to have a broader testing audience. I'll close this issue for now. @noctuid, thank's a lot for testing the intermediate versions. And, of course, @bearcatsandor, thanks for suggesting this in the first place. I had no idea this feature would be so useful ;-)

Possible future extensions:

ghost commented 5 years ago

I have long been looking for an image viewer that does this, the best example I could find was an experimental viewer named SeaDragon but I can't even find a binary for that.

It was showcased over 10 years ago on TED: https://www.ted.com/talks/blaise_aguera_y_arcas_demos_photosynth

Anyone knows where something like that can be found?

Arnaudv6 commented 4 years ago

@ghost, have you heard of galapix? (no release since 2014 though)