797 lines
26 KiB
Python
797 lines
26 KiB
Python
# -*- coding: utf-8 -*-
|
|
|
|
# Copyright © 2012-2017 marmuta <marmvta@gmail.com>
|
|
#
|
|
# This file is part of Onboard.
|
|
#
|
|
# Onboard is free software; you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation; either version 3 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# Onboard is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
""" Keyboard layout view """
|
|
|
|
from __future__ import division, print_function, unicode_literals
|
|
|
|
import time
|
|
from math import pi
|
|
|
|
import cairo
|
|
from Onboard.Version import require_gi_versions
|
|
require_gi_versions()
|
|
from gi.repository import Gtk, Gdk, GdkPixbuf
|
|
|
|
from Onboard.utils import Rect, \
|
|
roundrect_arc, roundrect_curve, \
|
|
gradient_line, brighten, \
|
|
unicode_str
|
|
from Onboard.WindowUtils import get_monitor_dimensions
|
|
from Onboard.KeyGtk import Key
|
|
from Onboard.KeyCommon import LOD
|
|
from Onboard.definitions import UIMask
|
|
|
|
|
|
### Logging ###
|
|
import logging
|
|
_logger = logging.getLogger("LayoutView")
|
|
###############
|
|
|
|
### Config Singleton ###
|
|
from Onboard.Config import Config
|
|
config = Config()
|
|
########################
|
|
|
|
class LayoutView:
|
|
"""
|
|
Viewer for a tree of layout items.
|
|
"""
|
|
|
|
def __init__(self, keyboard):
|
|
self.keyboard = keyboard
|
|
self.supports_alpha = False
|
|
|
|
self._lod = LOD.FULL
|
|
self._font_sizes_valid = False
|
|
self._shadow_quality_valid = False
|
|
self._last_canvas_shadow_rect = Rect()
|
|
|
|
self._starting_up = True
|
|
self._keys_pre_rendered = False
|
|
|
|
self.keyboard.register_view(self)
|
|
|
|
def cleanup(self):
|
|
self.keyboard.deregister_view(self)
|
|
|
|
# free xserver memory
|
|
self.invalidate_keys()
|
|
self.invalidate_shadows()
|
|
|
|
def handle_realize_event(self):
|
|
self.update_touch_input_mode()
|
|
self.update_input_event_source()
|
|
|
|
def on_layout_loaded(self):
|
|
""" Layout has been loaded. """
|
|
self.invalidate_shadow_quality()
|
|
|
|
def get_layout(self):
|
|
return self.keyboard.layout
|
|
|
|
def get_color_scheme(self):
|
|
return self.keyboard.color_scheme
|
|
|
|
def invalidate_for_resize(self, lod=LOD.FULL):
|
|
self.invalidate_keys()
|
|
if self._lod == LOD.FULL:
|
|
self.invalidate_shadows()
|
|
self.invalidate_font_sizes()
|
|
# self.invalidate_label_extents()
|
|
self.keyboard.invalidate_for_resize()
|
|
|
|
def invalidate_font_sizes(self):
|
|
"""
|
|
Update font_sizes at the next possible chance.
|
|
"""
|
|
self._font_sizes_valid = False
|
|
|
|
def invalidate_keys(self):
|
|
"""
|
|
Clear cached key surfaces, e.g. after resizing,
|
|
change of theme settings.
|
|
"""
|
|
layout = self.get_layout()
|
|
if layout:
|
|
for item in layout.iter_keys():
|
|
item.invalidate_key()
|
|
|
|
def invalidate_images(self):
|
|
"""
|
|
Clear cached images, e.g. after changing window_scaling_factor.
|
|
"""
|
|
layout = self.get_layout()
|
|
if layout:
|
|
for item in layout.iter_keys():
|
|
item.invalidate_image()
|
|
|
|
def invalidate_shadows(self):
|
|
"""
|
|
Clear cached shadow surfaces, e.g. after resizing,
|
|
change of theme settings.
|
|
"""
|
|
layout = self.get_layout()
|
|
if layout:
|
|
for item in layout.iter_keys():
|
|
item.invalidate_shadow()
|
|
|
|
def invalidate_shadow_quality(self):
|
|
self._shadow_quality_valid = False
|
|
|
|
def invalidate_label_extents(self):
|
|
"""
|
|
Clear cached resolution independent label extents, e.g.
|
|
after changes to the systems font dpi setting (gtk-xft-dpi).
|
|
"""
|
|
layout = self.get_layout()
|
|
if layout:
|
|
for item in layout.iter_keys():
|
|
item.invalidate_label_extents()
|
|
|
|
def reset_lod(self):
|
|
""" Reset to full level of detail """
|
|
if self._lod != LOD.FULL:
|
|
self._lod = LOD.FULL
|
|
self.invalidate_for_resize()
|
|
self.keyboard.invalidate_context_ui()
|
|
self.keyboard.invalidate_canvas()
|
|
self.keyboard.commit_ui_updates()
|
|
|
|
def is_visible(self):
|
|
return None
|
|
|
|
def set_visible(self, visible):
|
|
pass
|
|
|
|
def toggle_visible(self):
|
|
pass
|
|
|
|
def raise_to_top(self):
|
|
pass
|
|
|
|
def redraw(self, items=None, invalidate=True):
|
|
"""
|
|
Queue redrawing for individual keys or the whole keyboard.
|
|
"""
|
|
if items is None:
|
|
self.queue_draw()
|
|
|
|
elif len(items) == 0:
|
|
pass
|
|
|
|
else:
|
|
area = None
|
|
for item in items:
|
|
rect = item.get_canvas_border_rect()
|
|
area = area.union(rect) if area else rect
|
|
|
|
# assume keys need to be refreshed when actively redrawn
|
|
# e.g. for pressed state changes, dwell progress updates...
|
|
if invalidate and \
|
|
item.is_key():
|
|
item.invalidate_key()
|
|
|
|
# account for stroke width, anti-aliasing
|
|
if self.get_layout():
|
|
extra_size = items[0].get_extra_render_size()
|
|
area = area.inflate(*extra_size)
|
|
|
|
self.queue_draw_area(*area)
|
|
|
|
def redraw_labels(self, invalidate=True):
|
|
self.redraw(self.update_labels(), invalidate)
|
|
|
|
def update_transparency(self):
|
|
pass
|
|
|
|
def update_input_event_source(self):
|
|
self.register_input_events(True, config.is_event_source_gtk())
|
|
|
|
def update_touch_input_mode(self):
|
|
self.set_touch_input_mode(config.keyboard.touch_input)
|
|
|
|
def can_delay_sequence_begin(self, sequence):
|
|
"""
|
|
Veto gesture delay for move buttons. Have the keyboard start
|
|
moving right away and not lag behind the pointer.
|
|
"""
|
|
layout = self.get_layout()
|
|
if layout:
|
|
for item in layout.find_ids(["move"]):
|
|
if item.is_path_visible() and \
|
|
item.is_point_within(sequence.point):
|
|
return False
|
|
return True
|
|
|
|
def show_touch_handles(self, show, auto_hide):
|
|
pass
|
|
|
|
def apply_ui_updates(self, mask):
|
|
if mask & UIMask.SIZE:
|
|
self.invalidate_for_resize()
|
|
|
|
def update_layout(self):
|
|
pass
|
|
|
|
def process_updates(self):
|
|
""" Draw now, synchronously. """
|
|
window = self.get_window()
|
|
if window:
|
|
window.process_updates(True)
|
|
|
|
def render(self, context):
|
|
""" Pre-render key surfaces for instant initial drawing. """
|
|
|
|
# lazily update font sizes and labels
|
|
if not self._font_sizes_valid:
|
|
self.update_labels()
|
|
|
|
layout = self.get_layout()
|
|
if not layout:
|
|
return
|
|
|
|
self._auto_select_shadow_quality(context)
|
|
|
|
# run through all visible layout items
|
|
for item in layout.iter_visible_items():
|
|
if item.is_key():
|
|
item.draw_shadow_cached(context)
|
|
item.draw_cached(context)
|
|
|
|
self._keys_pre_rendered = True
|
|
|
|
def _can_draw_cached(self, lod):
|
|
"""
|
|
Draw cached key surfaces?
|
|
|
|
On first startup draw cached only if keys were pre-rendered, i.e. the
|
|
time to render keys was hidden before the window was shown.
|
|
|
|
We can't easily pre-render keys in xembed mode because the window size
|
|
is unknown in advance. Draw there once uncached instead (faster).
|
|
"""
|
|
return (lod == LOD.FULL) and \
|
|
(not self._starting_up or self._keys_pre_rendered)
|
|
|
|
def draw(self, widget, context):
|
|
if not Gtk.cairo_should_draw_window(context, widget.get_window()):
|
|
return
|
|
|
|
lod = self._lod
|
|
draw_cached = self._can_draw_cached(lod)
|
|
|
|
# lazily update font sizes and labels
|
|
if not self._font_sizes_valid:
|
|
self.update_labels(lod)
|
|
|
|
draw_rect = self.get_damage_rect(context)
|
|
|
|
# draw background
|
|
decorated = self._draw_background(context, lod)
|
|
|
|
layout = self.get_layout()
|
|
if not layout:
|
|
return
|
|
|
|
# draw layer 0 and None-layer background
|
|
layer_ids = layout.get_layer_ids()
|
|
if config.window.transparent_background:
|
|
alpha = 0.0
|
|
elif decorated:
|
|
alpha = self.get_background_rgba()[3]
|
|
else:
|
|
alpha = 1.0
|
|
self._draw_layer_key_background(context, alpha,
|
|
None, None, lod)
|
|
if layer_ids:
|
|
self._draw_layer_key_background(context, alpha,
|
|
None, layer_ids[0], lod)
|
|
|
|
# run through all visible layout items
|
|
for item in layout.iter_visible_items():
|
|
if item.layer_id:
|
|
self._draw_layer_background(context, item, layer_ids, decorated)
|
|
|
|
# draw key
|
|
if item.is_key() and \
|
|
draw_rect.intersects(item.get_canvas_border_rect()):
|
|
if draw_cached:
|
|
item.draw_cached(context)
|
|
else:
|
|
item.draw(context, lod)
|
|
|
|
self._starting_up = False
|
|
|
|
return decorated
|
|
|
|
def _draw_background(self, context, lod):
|
|
""" Draw keyboard background """
|
|
transparent_bg = False
|
|
plain_bg = False
|
|
|
|
if config.is_keep_xembed_frame_aspect_ratio_enabled():
|
|
if self.supports_alpha:
|
|
self._clear_xembed_background(context)
|
|
transparent_bg = True
|
|
else:
|
|
plain_bg = True
|
|
|
|
elif config.xid_mode:
|
|
# xembed mode
|
|
# Disable transparency in lightdm and g-s-s for now.
|
|
# There are too many issues and there is no real
|
|
# visual improvement.
|
|
plain_bg = True
|
|
|
|
elif config.has_window_decoration():
|
|
# decorated window
|
|
if self.supports_alpha and \
|
|
config.window.transparent_background:
|
|
self._clear_background(context)
|
|
else:
|
|
plain_bg = True
|
|
|
|
else:
|
|
# undecorated window
|
|
if self.supports_alpha:
|
|
self._clear_background(context)
|
|
if not config.window.transparent_background:
|
|
transparent_bg = True
|
|
else:
|
|
plain_bg = True
|
|
|
|
if plain_bg:
|
|
self._draw_plain_background(context)
|
|
if transparent_bg:
|
|
self._draw_transparent_background(context, lod)
|
|
|
|
return transparent_bg
|
|
|
|
def _clear_background(self, context):
|
|
"""
|
|
Clear the whole gtk background.
|
|
Makes the whole strut transparent in xembed mode.
|
|
"""
|
|
context.save()
|
|
context.set_operator(cairo.OPERATOR_CLEAR)
|
|
context.paint()
|
|
context.restore()
|
|
|
|
def _clear_xembed_background(self, context):
|
|
""" fill with plain layer 0 color; no alpha support required """
|
|
rect = Rect(0, 0, self.get_allocated_width(),
|
|
self.get_allocated_height())
|
|
|
|
# draw background image
|
|
if config.get_xembed_background_image_enabled():
|
|
pixbuf = self._get_xembed_background_image()
|
|
if pixbuf:
|
|
src_size = (pixbuf.get_width(), pixbuf.get_height())
|
|
x, y = 0, rect.bottom() - src_size[1]
|
|
Gdk.cairo_set_source_pixbuf(context, pixbuf, x, y)
|
|
context.paint()
|
|
|
|
# draw solid colored bar on top (with transparency, usually)
|
|
rgba = config.get_xembed_background_rgba()
|
|
if rgba is None:
|
|
rgba = self.get_background_rgba()
|
|
rgba[3] = 0.5
|
|
context.set_source_rgba(*rgba)
|
|
context.rectangle(*rect)
|
|
context.fill()
|
|
|
|
def _get_xembed_background_image(self):
|
|
""" load the desktop background image in Unity """
|
|
try:
|
|
pixbuf = self._xid_background_image
|
|
except AttributeError:
|
|
size, size_mm = get_monitor_dimensions(self)
|
|
filename = config.get_desktop_background_filename()
|
|
if not filename or \
|
|
size[0] <= 0 or size[1] <= 0:
|
|
pixbuf = None
|
|
else:
|
|
try:
|
|
# load image
|
|
pixbuf = GdkPixbuf.Pixbuf.new_from_file(filename)
|
|
|
|
# Scale image to mimic the behavior of gnome-screen-saver.
|
|
# Take the largest, aspect correct, centered rectangle
|
|
# that fits on the monitor.
|
|
rm = Rect(0, 0, size[0], size[1])
|
|
rp = Rect(0, 0, pixbuf.get_width(), pixbuf.get_height())
|
|
ra = rm.inscribe_with_aspect(rp)
|
|
pixbuf = pixbuf.new_subpixbuf(*ra)
|
|
pixbuf = pixbuf.scale_simple(size[0], size[1],
|
|
GdkPixbuf.InterpType.BILINEAR)
|
|
except Exception as ex: # private exception gi._glib.GError when
|
|
# librsvg2-common wasn't installed
|
|
_logger.error("_get_xembed_background_image(): " + \
|
|
unicode_str(ex))
|
|
pixbuf = None
|
|
|
|
self._xid_background_image = pixbuf
|
|
|
|
return pixbuf
|
|
|
|
def _draw_transparent_background(self, context, lod):
|
|
""" fill with the transparent background color """
|
|
corner_radius = config.CORNER_RADIUS
|
|
rect = self.get_keyboard_frame_rect()
|
|
fill = self.get_background_rgba()
|
|
|
|
if self.can_draw_sidebars():
|
|
self._draw_side_bars(context)
|
|
|
|
fill_gradient = config.theme_settings.background_gradient
|
|
if lod == LOD.MINIMAL or \
|
|
fill_gradient == 0:
|
|
context.set_source_rgba(*fill)
|
|
else:
|
|
fill_gradient /= 100.0
|
|
direction = config.theme_settings.key_gradient_direction
|
|
alpha = -pi/2.0 + pi * direction / 180.0
|
|
gline = gradient_line(rect, alpha)
|
|
|
|
pat = cairo.LinearGradient (*gline)
|
|
rgba = brighten(+fill_gradient*.5, *fill)
|
|
pat.add_color_stop_rgba(0, *rgba)
|
|
rgba = brighten(-fill_gradient*.5, *fill)
|
|
pat.add_color_stop_rgba(1, *rgba)
|
|
context.set_source (pat)
|
|
|
|
if config.xid_mode:
|
|
frame = False
|
|
else:
|
|
frame = self.can_draw_frame()
|
|
|
|
if frame:
|
|
roundrect_arc(context, rect, corner_radius)
|
|
else:
|
|
context.rectangle(*rect)
|
|
|
|
context.fill()
|
|
|
|
if frame:
|
|
self.draw_window_frame(context, lod)
|
|
self.draw_keyboard_frame(context, lod)
|
|
|
|
def _draw_side_bars(self, context):
|
|
"""
|
|
Transparent bars left and right of the aspect corrected
|
|
keyboard frame.
|
|
"""
|
|
rgba = self.get_background_rgba()
|
|
rgba[3] = 0.5
|
|
rwin = Rect(0, 0,
|
|
self.get_allocated_width(),
|
|
self.get_allocated_height())
|
|
rframe = self.get_keyboard_frame_rect()
|
|
|
|
if rwin.w > rframe.w:
|
|
r = rframe.copy()
|
|
context.set_source_rgba(*rgba)
|
|
context.set_line_width(0)
|
|
|
|
r.x = rwin.left()
|
|
r.w = rframe.left() - rwin.left()
|
|
context.rectangle(*r)
|
|
context.fill()
|
|
|
|
r.x = rframe.right()
|
|
r.w = rwin.right() - rframe.right()
|
|
context.rectangle(*r)
|
|
context.fill()
|
|
|
|
def can_draw_frame(self):
|
|
""" overloaded in KeyboardWidget """
|
|
return True
|
|
|
|
def can_draw_sidebars(self):
|
|
""" overloaded in KeyboardWidget """
|
|
return False
|
|
|
|
def draw_window_frame(self, context, lod):
|
|
pass
|
|
|
|
def draw_keyboard_frame(self, context, lod):
|
|
""" draw frame around the (potentially aspect corrected) keyboard """
|
|
corner_radius = config.CORNER_RADIUS
|
|
rect = self.get_keyboard_frame_rect()
|
|
fill = self.get_background_rgba()
|
|
|
|
# inner decoration line
|
|
line_rect = rect.deflate(1)
|
|
roundrect_arc(context, line_rect, corner_radius)
|
|
context.stroke()
|
|
|
|
def _draw_plain_background(self, context, layer_index = 0):
|
|
""" fill with plain layer 0 color; no alpha support required """
|
|
rgba = self._get_layer_fill_rgba(layer_index)
|
|
context.set_source_rgba(*rgba)
|
|
context.paint()
|
|
|
|
def _draw_layer_background(self, context, item, layer_ids, decorated):
|
|
# layer background
|
|
layer_index = layer_ids.index(item.layer_id)
|
|
parent = item.parent
|
|
if parent and \
|
|
layer_index != 0:
|
|
rect = parent.get_canvas_rect()
|
|
context.rectangle(*rect.inflate(1))
|
|
|
|
color_scheme = self.get_color_scheme()
|
|
if color_scheme:
|
|
rgba = color_scheme.get_layer_fill_rgba(layer_index)
|
|
else:
|
|
rgba = [0.5, 0.5, 0.5, 0.9]
|
|
context.set_source_rgba(*rgba)
|
|
context.fill()
|
|
|
|
# per-layer key background
|
|
self._draw_layer_key_background(context, 1.0, item, item.layer_id)
|
|
|
|
def _draw_layer_key_background(self, context, alpha = 1.0, item = None,
|
|
layer_id = None, lod = LOD.FULL):
|
|
self._draw_dish_key_background(context, alpha, item, layer_id)
|
|
self._draw_shadows(context, layer_id, lod)
|
|
|
|
def _draw_dish_key_background(self, context, alpha = 1.0, item = None,
|
|
layer_id = None):
|
|
"""
|
|
Black background following the contours of key clusters
|
|
to simulate the opening in the keyboard plane.
|
|
"""
|
|
if config.theme_settings.key_style == "dish":
|
|
layout = self.get_layout()
|
|
context.push_group()
|
|
|
|
context.set_source_rgba(0, 0, 0, 1)
|
|
enlargement = layout.context.scale_log_to_canvas((0.8, 0.8))
|
|
corner_radius = layout.context.scale_log_to_canvas_x(2.4)
|
|
|
|
if item is None:
|
|
item = layout
|
|
|
|
for key in item.iter_layer_keys(layer_id):
|
|
rect = key.get_canvas_fullsize_rect()
|
|
rect = rect.inflate(*enlargement)
|
|
roundrect_curve(context, rect, corner_radius)
|
|
context.fill()
|
|
|
|
context.pop_group_to_source()
|
|
context.paint_with_alpha(alpha);
|
|
|
|
def _draw_shadows(self, context, layer_id, lod):
|
|
"""
|
|
Draw drop shadows for all keys.
|
|
"""
|
|
# Shadows are drawn at odd positions when resizing while
|
|
# docked and extended with side bars visible.
|
|
# -> Turn them off while resizing. Improves rendering speed a bit too.
|
|
if lod < LOD.FULL:
|
|
return
|
|
if not config.theme_settings.key_shadow_strength:
|
|
return
|
|
|
|
self._auto_select_shadow_quality(context)
|
|
|
|
context.save()
|
|
self.set_shadow_scale(context, lod)
|
|
|
|
draw_rect = self.get_damage_rect(context)
|
|
layout = self.get_layout()
|
|
for item in layout.iter_layer_keys(layer_id):
|
|
if draw_rect.intersects(item.get_canvas_border_rect()):
|
|
item.draw_shadow_cached(context)
|
|
|
|
context.restore()
|
|
|
|
def _auto_select_shadow_quality(self, context):
|
|
""" auto-select shadow quality """
|
|
if not self._shadow_quality_valid:
|
|
quality = self._probe_shadow_performance(context)
|
|
Key.set_shadow_quality(quality)
|
|
self._shadow_quality_valid = True
|
|
|
|
def _probe_shadow_performance(self, context):
|
|
"""
|
|
Determine shadow quality based on the estimated render time of
|
|
the first layer's shadows.
|
|
"""
|
|
probe_begin = time.time()
|
|
quality = None
|
|
|
|
layout = self.get_layout()
|
|
max_total_time = 0.03 # upper limit refreshing all key's shadows [s]
|
|
max_probe_keys = 10
|
|
keys = None
|
|
for layer_id in layout.get_layer_ids():
|
|
layer_keys = list(layout.iter_layer_keys(layer_id))
|
|
num_first_layer_keys = len(layer_keys)
|
|
keys = layer_keys[:max_probe_keys]
|
|
break
|
|
|
|
if keys:
|
|
for quality, (steps, alpha) in enumerate(Key._shadow_presets):
|
|
begin = time.time()
|
|
for key in keys:
|
|
key.create_shadow_surface(context, steps, 0.1)
|
|
elapsed = time.time() - begin
|
|
estimate = elapsed / len(keys) * num_first_layer_keys
|
|
_logger.debug("Probing shadow performance: "
|
|
"estimated full refresh time {:6.1f}ms "
|
|
"at quality {}, {} steps." \
|
|
.format(estimate * 1000,
|
|
quality, steps))
|
|
if estimate > max_total_time:
|
|
break
|
|
|
|
_logger.info("Probing shadow performance took {:.1f}ms. "
|
|
"Selecting quality {}." \
|
|
.format((time.time() - probe_begin) * 1000,
|
|
quality))
|
|
return quality
|
|
|
|
def set_shadow_scale(self, context, lod):
|
|
"""
|
|
Shadows aren't normally refreshed while resizing.
|
|
-> scale the cached ones to fit the new canvas size.
|
|
Occasionally refresh them anyway if scaling becomes noticeable.
|
|
"""
|
|
r = self.get_keyboard_frame_rect()
|
|
if lod < LOD.FULL:
|
|
rl = self._last_canvas_shadow_rect
|
|
scale_x = r.w / rl.w
|
|
scale_y = r.h / rl.h
|
|
|
|
# scale in a reasonable range? -> draw stretched shadows
|
|
smin = 0.8
|
|
smax = 1.2
|
|
if smax > scale_x > smin and \
|
|
smax > scale_y > smin:
|
|
context.scale(scale_x, scale_y)
|
|
else:
|
|
# else scale is too far out -> refresh shadows
|
|
self.invalidate_shadows()
|
|
self._last_canvas_shadow_rect = r
|
|
else:
|
|
self._last_canvas_shadow_rect = r
|
|
|
|
def _get_layer_fill_rgba(self, layer_index):
|
|
color_scheme = self.get_color_scheme()
|
|
if color_scheme:
|
|
return color_scheme.get_layer_fill_rgba(layer_index)
|
|
else:
|
|
return [0.5, 0.5, 0.5, 1.0]
|
|
|
|
def get_background_rgba(self):
|
|
""" layer 0 color * background_transparency """
|
|
layer0_rgba = self._get_layer_fill_rgba(0)
|
|
background_alpha = config.window.get_background_opacity()
|
|
background_alpha *= layer0_rgba[3]
|
|
return layer0_rgba[:3] + [background_alpha]
|
|
|
|
def get_popup_window_rgba(self, element = "border"):
|
|
color_scheme = self.get_color_scheme()
|
|
if color_scheme:
|
|
rgba = color_scheme.get_window_rgba("key-popup", element)
|
|
else:
|
|
rgba = [0.8, 0.8, 0.8, 1.0]
|
|
background_alpha = config.window.get_background_opacity()
|
|
background_alpha *= rgba[3]
|
|
return rgba[:3] + [background_alpha]
|
|
|
|
def get_damage_rect(self, context):
|
|
clip_rect = Rect.from_extents(*context.clip_extents())
|
|
|
|
# Draw a little more than just the clip_rect.
|
|
# Prevents glitches around pressed keys in at least classic theme.
|
|
layout = self.get_layout()
|
|
if layout:
|
|
extra_size = layout.context.scale_log_to_canvas((2.0, 2.0))
|
|
else:
|
|
extra_size = 0, 0
|
|
return clip_rect.inflate(*extra_size)
|
|
|
|
def get_keyboard_frame_rect(self):
|
|
"""
|
|
Rectangle of the potentially aspect-corrected
|
|
frame around the layout.
|
|
"""
|
|
layout = self.get_layout()
|
|
if layout:
|
|
rect = layout.get_canvas_border_rect()
|
|
rect = rect.inflate(self.get_frame_width())
|
|
else:
|
|
rect = Rect(0, 0, self.get_allocated_width(),
|
|
self.get_allocated_height())
|
|
return rect.int()
|
|
|
|
def is_docking_expanded(self):
|
|
return self.window.docking_enabled and self.window.docking_expanded
|
|
|
|
|
|
def update_labels(self, lod = LOD.FULL):
|
|
"""
|
|
Iterate through all key groups and set each key's
|
|
label font size to the maximum possible for that group.
|
|
"""
|
|
changed_keys = set()
|
|
layout = self.get_layout()
|
|
|
|
mod_mask = self.keyboard.get_mod_mask()
|
|
|
|
if layout:
|
|
if lod == LOD.FULL: # no label changes necessary while dragging
|
|
|
|
for key in layout.iter_keys():
|
|
old_label = key.get_label()
|
|
key.configure_label(mod_mask)
|
|
if key.get_label() != old_label:
|
|
changed_keys.add(key)
|
|
|
|
for keys in layout.get_key_groups().values():
|
|
max_size = 0
|
|
for key in keys:
|
|
best_size = key.get_best_font_size(mod_mask)
|
|
if best_size:
|
|
if key.ignore_group:
|
|
if key.font_size != best_size:
|
|
key.font_size = best_size
|
|
changed_keys.add(key)
|
|
else:
|
|
if not max_size or best_size < max_size:
|
|
max_size = best_size
|
|
|
|
for key in keys:
|
|
if key.font_size != max_size and \
|
|
not key.ignore_group:
|
|
key.font_size = max_size
|
|
changed_keys.add(key)
|
|
|
|
self._font_sizes_valid = True
|
|
return tuple(changed_keys)
|
|
|
|
def get_key_at_location(self, point):
|
|
layout = self.get_layout()
|
|
keyboard = self.keyboard
|
|
if layout and keyboard: # may be gone on exit
|
|
return layout.get_key_at(point, keyboard.active_layer)
|
|
return None
|
|
|
|
def get_xid(self):
|
|
# Zesty, X, Gtk 3.22: XInput select_events() on self leads to
|
|
# LP: #1636252. On the first call to get_xid() of a child widget,
|
|
# Gtk creates a new native X Window with broken transparency.
|
|
# The toplevel window ought to always have a native X window, so
|
|
# we'll pick that one instead and skip on-the fly creation.
|
|
# TouchInput isn't used for anything other than full client areas
|
|
# yet, so in principle this shouldn't be a problem.
|
|
|
|
toplevel = self.get_toplevel()
|
|
if toplevel:
|
|
topwin = toplevel.get_window()
|
|
if topwin:
|
|
return topwin.get_xid()
|
|
return 0
|
|
|
|
|