Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions data/net.launchpad.Diodon.gschema.xml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@
<summary>Number of recent clipboard items</summary>
<description>Number of recent clipboard items shown in clipboard menu.</description>
</key>
<key name="pinned-items" type="as">
<default>[]</default>
<summary>Pinned clipboard items</summary>
<description>Checksums of clipboard items that are pinned to the top of the menu and preserved on clear.</description>
</key>
<key name="filter-pattern" type="s">
<default>'^\\s+$'</default>
<summary>Clipboard content filter pattern</summary>
Expand Down
7 changes: 7 additions & 0 deletions libdiodon/clipboard-item.vala
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,13 @@ namespace Diodon
*/
public abstract Gtk.Image? get_image();

/**
* preview pixbuf for tooltip display
*
* @return scaled preview pixbuf or null if not available
*/
public abstract Gdk.Pixbuf? get_preview_pixbuf();

/**
* icon to represent type of clipboard item
*
Expand Down
97 changes: 97 additions & 0 deletions libdiodon/clipboard-menu-item.vala
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ namespace Diodon
class ClipboardMenuItem : Gtk.ImageMenuItem
{
private string _checksum;
private IClipboardItem _item;
private Gdk.Pixbuf? _cached_preview;
private bool _preview_loaded;
private static Gtk.Window? _preview_window;

/**
* Clipboard item constructor
Expand All @@ -37,6 +41,7 @@ namespace Diodon
public ClipboardMenuItem(IClipboardItem item)
{
_checksum = item.get_checksum();
_item = item;
set_label(item.get_label());

// check if image needs to be shown
Expand All @@ -45,6 +50,98 @@ namespace Diodon
set_image(image);
set_always_show_image(true);
}

debug("ClipboardMenuItem: connecting select/deselect for %s", _checksum);
select.connect(on_item_select);
deselect.connect(on_item_deselect);
}

private Gdk.Pixbuf? get_preview()
{
if (!_preview_loaded) {
_cached_preview = _item.get_preview_pixbuf();
_preview_loaded = true;
}
return _cached_preview;
}

private void on_item_select()
{
debug("on_item_select: checksum=%s", _checksum);
Gdk.Pixbuf? preview = get_preview();
if (preview == null) {
debug("on_item_select: no preview available");
return;
}
debug("on_item_select: preview %dx%d", preview.width, preview.height);
show_preview_pixbuf(preview);
}

public static void show_preview_for(IClipboardItem item)
{
Gdk.Pixbuf? preview = item.get_preview_pixbuf();
if (preview == null) {
return;
}
show_preview_pixbuf(preview);
}

private static void show_preview_pixbuf(Gdk.Pixbuf preview)
{
if (_preview_window == null) {
_preview_window = new Gtk.Window(Gtk.WindowType.POPUP);
_preview_window.set_type_hint(Gdk.WindowTypeHint.TOOLTIP);
_preview_window.set_app_paintable(true);
Gdk.Screen screen = _preview_window.get_screen();
Gdk.Visual? visual = screen.get_rgba_visual();
if (visual != null) {
_preview_window.set_visual(visual);
}
}
_preview_window.foreach((child) => { _preview_window.remove(child); });
Gtk.Image preview_image = new Gtk.Image.from_pixbuf(preview);
Gtk.CssProvider css = new Gtk.CssProvider();
try {
css.load_from_data("frame { border: 1px solid #888888; }", -1);
} catch (Error e) {
warning("Failed to load preview CSS: %s", e.message);
}
Gtk.Frame frame = new Gtk.Frame(null);
frame.set_shadow_type(Gtk.ShadowType.NONE);
frame.get_style_context().add_provider(css, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION);
frame.add(preview_image);
_preview_window.add(frame);
_preview_window.resize(preview.width, preview.height);
Gdk.Display? display = Gdk.Display.get_default();
Gdk.Seat? seat = display != null ? display.get_default_seat() : null;
if (seat != null) {
int mouse_x, mouse_y;
seat.get_pointer().get_position(null, out mouse_x, out mouse_y);
int x = mouse_x + 16;
int y = mouse_y + 16;
Gdk.Screen screen = _preview_window.get_screen();
int screen_height = screen.get_height();
if (y + preview.height > screen_height) {
y = screen_height - preview.height;
}
if (y < 0) {
y = 0;
}
_preview_window.move(x, y);
}
_preview_window.show_all();
}

private static void on_item_deselect()
{
hide_preview();
}

public static void hide_preview()
{
if (_preview_window != null) {
_preview_window.hide();
}
}

/**
Expand Down
80 changes: 74 additions & 6 deletions libdiodon/clipboard-menu.vala
Original file line number Diff line number Diff line change
Expand Up @@ -28,16 +28,19 @@ namespace Diodon
{
private Controller controller;
private unowned List<Gtk.Widget> static_menu_items;
private int _saved_menu_x = -1;
private int _saved_menu_y = -1;

/**
* Create clipboard menu
*
* @param controller reference to controller
* @param pinned_items pinned clipboard items shown at the top
* @param items clipboard items to be shown
* @param menu_items additional menu items to be added after separator
* @param privacy_mode check whether privacy mode is enabled
*/
public ClipboardMenu(Controller controller, List<IClipboardItem> items, List<Gtk.MenuItem>? static_menu_items, bool privace_mode, string? error = null)
public ClipboardMenu(Controller controller, List<IClipboardItem> pinned_items, List<IClipboardItem> items, List<Gtk.MenuItem>? static_menu_items, bool privace_mode, string? error = null)
{
this.controller = controller;
this.static_menu_items = static_menu_items;
Expand All @@ -46,7 +49,7 @@ namespace Diodon
Gtk.MenuItem error_item = new Gtk.MenuItem.with_label(wrap_label(error));
error_item.set_sensitive(false);
append(error_item);
} else if(items.length() <= 0) {
} else if(pinned_items.length() <= 0 && items.length() <= 0) {
Gtk.MenuItem empty_item = new Gtk.MenuItem.with_label(_("<Empty>"));
empty_item.set_sensitive(false);
append(empty_item);
Expand All @@ -60,9 +63,17 @@ namespace Diodon
append(privacy_item);
}

foreach(IClipboardItem item in pinned_items) {
append_clipboard_item(item, true);
}

if (pinned_items.length() > 0 && items.length() > 0) {
Gtk.SeparatorMenuItem pin_sep = new Gtk.SeparatorMenuItem();
append(pin_sep);
}

foreach(IClipboardItem item in items) {
append_clipboard_item(item);
append_clipboard_item(item, false);
}

Gtk.SeparatorMenuItem sep_item = new Gtk.SeparatorMenuItem();
Expand All @@ -89,21 +100,68 @@ namespace Diodon
show_all();

this.key_press_event.connect(on_key_pressed);
this.hide.connect(() => { ClipboardMenuItem.hide_preview(); });
this.map.connect(() => {
if (get_window() != null) {
int x, y;
get_window().get_origin(out x, out y);
_saved_menu_x = x;
_saved_menu_y = y;
}
});
}

/**
* Append given clipboard item to menu.
*
* @param entry entry to be added
* @param item clipboard item to add
* @param pinned whether the item is pinned
*/
public void append_clipboard_item(IClipboardItem item)
public void append_clipboard_item(IClipboardItem item, bool pinned = false)
{
ClipboardMenuItem menu_item = new ClipboardMenuItem(item);
menu_item.activate.connect(on_clicked_item);
menu_item.button_press_event.connect((event) => {
if (event.button == 3) {
show_pin_context_menu(menu_item, pinned, event);
return true;
}
return false;
});
menu_item.show();
append(menu_item);
}

private void show_pin_context_menu(ClipboardMenuItem menu_item, bool pinned, Gdk.EventButton event)
{
Gtk.Menu ctx = new Gtk.Menu();
string label = pinned ? _("Unpin") : _("Pin");
Gtk.MenuItem pin_item = new Gtk.MenuItem.with_label(label);
int reopen_x = _saved_menu_x;
int reopen_y = _saved_menu_y;
pin_item.activate.connect(() => {
controller.toggle_pin_item.begin(menu_item.get_item_checksum(), false);
});
ctx.append(pin_item);
ctx.show_all();
ctx.deactivate.connect(() => {
controller.rebuild_recent_menu.begin((obj, res) => {
controller.rebuild_recent_menu.end(res);
if (reopen_x >= 0 && reopen_y >= 0) {
Gtk.Menu new_menu = controller.get_recent_menu() as Gtk.Menu;
if (new_menu != null) {
new_menu.popup(null, null, (menu, ref x, ref y, out push_in) => {
x = reopen_x;
y = reopen_y;
push_in = false;
}, 0, Gtk.get_current_event_time());
}
}
});
});
ctx.popup_at_pointer(event);
}

public void show_menu()
{
// timer is needed to workaround race condition between X11 and Gdk event
Expand Down Expand Up @@ -181,12 +239,14 @@ namespace Diodon
}

/**
* Allow moving of cursor with vi-style j and k keys
* Allow moving of cursor with vi-style j and k keys,
* and p to toggle pin on selected item.
*/
private bool on_key_pressed(Gdk.EventKey event)
{
uint down_keyval = Gdk.keyval_from_name("j");
uint up_keyval = Gdk.keyval_from_name("k");
uint pin_keyval = Gdk.keyval_from_name("p");

uint pressed_keyval = Gdk.keyval_to_lower(event.keyval);
if(pressed_keyval == down_keyval) {
Expand All @@ -204,6 +264,14 @@ namespace Diodon
move_selected(-1);
return true;
}
if(pressed_keyval == pin_keyval) {
Gtk.MenuItem? selected = get_selected_item() as Gtk.MenuItem;
if (selected != null && selected is ClipboardMenuItem) {
ClipboardMenuItem clip_item = (ClipboardMenuItem) selected;
controller.toggle_pin_item.begin(clip_item.get_item_checksum());
}
return true;
}

return false;
}
Expand Down
Loading