Skip to content
Snippets Groups Projects
space_view3d_screencast_keys.py 29.7 KiB
Newer Older
# ##### BEGIN GPL LICENSE BLOCK #####
#
#  This program 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 2
#  of the License, or (at your option) any later version.
#
#  This program 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, write to the Free Software Foundation,
#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####

Bart Crouch's avatar
Bart Crouch committed
# <pep8 compliant>

Pablo Vazquez's avatar
Pablo Vazquez committed
    'name': "Screencast Keys",
    'author': 'Paulo Gomes, Bart Crouch, John E. Herrenyo, Gaia Clary, Pablo Vazquez',
Pablo Vazquez's avatar
Pablo Vazquez committed
    'version': (1, 7),
Pablo Vazquez's avatar
Pablo Vazquez committed
    'blender': (2, 6, 4),
Pablo Vazquez's avatar
Pablo Vazquez committed
    'location': '3D View > Properties Panel > Screencast Keys',
Pablo Vazquez's avatar
Pablo Vazquez committed
    'description': 'Display keys pressed in the 3D View, '\
Pablo Vazquez's avatar
Pablo Vazquez committed
    'wiki_url': 'http://wiki.blender.org/index.php/Extensions:2.6/'
Campbell Barton's avatar
Campbell Barton committed
                'Py/Scripts/3D_interaction/Screencast_Key_Status_Tool',
    'tracker_url': 'http://projects.blender.org/tracker/index.php?'
                   'func=detail&aid=21612',
    'category': '3D View'}

import bgl
import blf
import bpy
import time
Pablo Vazquez's avatar
Pablo Vazquez committed
import datetime
Bart Crouch's avatar
Bart Crouch committed

MOUSE_RATIO = 0.535

Bart Crouch's avatar
Bart Crouch committed

def getDisplayLocation(context):
Bart Crouch's avatar
Bart Crouch committed
    scene   = context.scene
    mouse_size = scene.screencast_keys_mouse_size

    pos_x = int( (context.region.width  - mouse_size * MOUSE_RATIO) * \
        scene.screencast_keys_pos_x / 100)
Campbell Barton's avatar
Campbell Barton committed
    pos_y = int( (context.region.height - mouse_size) *
Bart Crouch's avatar
Bart Crouch committed
        scene.screencast_keys_pos_y / 100)

    return(pos_x, pos_y)


def getBoundingBox(current_width, current_height, new_text):
    w,h = blf.dimensions(0,new_text)
    if w > current_width:
        current_width = w
    current_height += h
Bart Crouch's avatar
Bart Crouch committed

    return(current_width, current_height)

Pablo Vazquez's avatar
Pablo Vazquez committed
def draw_callback_px_text(self, context):
    sc = context.scene
    if not wm.screencast_keys_keys:
Bart Crouch's avatar
Bart Crouch committed

    font_size  = sc.screencast_keys_font_size
    mouse_size = sc.screencast_keys_mouse_size
Pablo Vazquez's avatar
Pablo Vazquez committed
    box_draw   = sc.screencast_keys_box_draw
    pos_x, pos_y = getDisplayLocation(context)
Pablo Vazquez's avatar
Pablo Vazquez committed
    label_time_max = sc.screencast_keys_fade_time
Pablo Vazquez's avatar
Pablo Vazquez committed
    # draw text in the 3D View
    blf.size(0, sc.screencast_keys_font_size, 72)
Pablo Vazquez's avatar
Pablo Vazquez committed
    blf.enable(0, blf.SHADOW)
    blf.shadow_offset(0, 1, -1)
    blf.shadow(0, 5, 0.0, 0.0, 0.0, 0.8)

    font_color_r, font_color_g, font_color_b, font_color_alpha = sc.screencast_keys_text_color
    row_count = len(self.key)

    keypos_x = pos_x
Pablo Vazquez's avatar
Pablo Vazquez committed

    if sc.screencast_keys_mouse_position == 'left':
Pablo Vazquez's avatar
Pablo Vazquez committed
        keypos_x += mouse_size * MOUSE_RATIO * 1.7
Pablo Vazquez's avatar
Pablo Vazquez committed
    if sc.screencast_keys_mouse != 'icon':
        keypos_x -= mouse_size * MOUSE_RATIO
    if sc.screencast_keys_mouse_position == 'right' and sc.screencast_keys_mouse != 'icon':
        keypos_x = pos_x

Pablo Vazquez's avatar
Pablo Vazquez committed

    # we want to make sure we can shift vertically the text if the mouse is big,
    # but don't care if aligned to right 
    if mouse_size > font_size*row_count and not sc.screencast_keys_mouse_position == 'right':
        shift = (mouse_size - font_size*row_count) / 2

    text_width, text_height = 0,0
    row_count = 0
    alpha = 1.0
    for i in range(len(self.key)):
        label_time = time.time() - self.time[i]
Pablo Vazquez's avatar
Pablo Vazquez committed
        if label_time < label_time_max: # only display key-presses of last 2 seconds
            if label_time > (label_time_max / 1.2):
Pablo Vazquez's avatar
Pablo Vazquez committed
                blf.blur(0, 1)
Pablo Vazquez's avatar
Pablo Vazquez committed
            if label_time > (label_time_max / 1.1):
Pablo Vazquez's avatar
Pablo Vazquez committed
                blf.blur(0, 3)
Campbell Barton's avatar
Campbell Barton committed
            keypos_y = pos_y + shift + font_size*(i+0.1)

            blf.position(0, keypos_x, keypos_y , 0)
Pablo Vazquez's avatar
Pablo Vazquez committed
            alpha = min(1.0, max(0.0, label_time_max * (label_time_max - label_time)))
Pablo Vazquez's avatar
Pablo Vazquez committed
            bgl.glColor4f(font_color_r, font_color_g, font_color_b, font_color_alpha * alpha)
Bart Crouch's avatar
Bart Crouch committed
            text_width, text_height = getBoundingBox(text_width, text_height,
                self.key[i])
Bart Crouch's avatar
Bart Crouch committed
            final = i + 1
Pablo Vazquez's avatar
Pablo Vazquez committed
    # remove blurriness 

    # disable shadows so they don't appear all over blender
    blf.blur(0,0)
    blf.disable(0, blf.SHADOW)

    # get rid of status texts that aren't displayed anymore
Bart Crouch's avatar
Bart Crouch committed
    self.key = self.key[:final]
    self.time = self.time[:final]

    # draw graphical representation of the mouse
    if sc.screencast_keys_mouse == 'icon':
        for shape in ["mouse", "left_button", "middle_button", "right_button"]:
Pablo Vazquez's avatar
Pablo Vazquez committed
            draw_mouse(context, shape, "outline", font_color_alpha * 0.4)
        for i in range(len(self.mouse)):
            click_time = time.time() - self.mouse_time[i]
            if click_time < 2:
                shape = map_mouse_event(self.mouse[i])
                if shape:
                    alpha = min(1.0, max(0.0, 2 * (2 - click_time)))
                    draw_mouse(context, shape, "filled", alpha)
Bart Crouch's avatar
Bart Crouch committed
                final = i + 1
    # get rid of mouse clicks that aren't displayed anymore
Bart Crouch's avatar
Bart Crouch committed
    self.mouse = self.mouse[:final]
    self.mouse_time = self.mouse_time[:final]
Pablo Vazquez's avatar
Pablo Vazquez committed
def draw_callback_px_box(self, context):
    wm = context.window_manager
    sc = context.scene
Pablo Vazquez's avatar
Pablo Vazquez committed
    if not wm.screencast_keys_keys:
        return

    font_size  = sc.screencast_keys_font_size
    mouse_size = sc.screencast_keys_mouse_size
Pablo Vazquez's avatar
Pablo Vazquez committed

    if sc.screencast_keys_mouse_position == 'right':
        mouse_size = 25

Pablo Vazquez's avatar
Pablo Vazquez committed
    box_draw   = sc.screencast_keys_box_draw
    pos_x, pos_y = getDisplayLocation(context)

    # get text-width/height to resize the box
    blf.size(0, sc.screencast_keys_font_size, 72)
    box_width, box_height = sc.screencast_keys_box_width,0
    final = 0
    row_count = 0
Pablo Vazquez's avatar
Pablo Vazquez committed
    box_hide = sc.screencast_keys_box_hide
    label_time_max = sc.screencast_keys_fade_time
Pablo Vazquez's avatar
Pablo Vazquez committed

    for i in range(len(self.key)):
        label_time = time.time() - self.time[i]
Pablo Vazquez's avatar
Pablo Vazquez committed

        if label_time < label_time_max: # only display key-presses of last 4 seconds
Pablo Vazquez's avatar
Pablo Vazquez committed
            box_width, box_height = getBoundingBox(box_width, box_height, self.key[i])
            row_count += 1
            final = i + 1
Pablo Vazquez's avatar
Pablo Vazquez committed
            box_hide = False
Pablo Vazquez's avatar
Pablo Vazquez committed
        else:
            break

Pablo Vazquez's avatar
Pablo Vazquez committed
    # Got the size right, now draw box using proper colors
Pablo Vazquez's avatar
Pablo Vazquez committed
    box_color_r, box_color_g, box_color_b, box_color_alpha = sc.screencast_keys_box_color

Pablo Vazquez's avatar
Pablo Vazquez committed
    if box_draw and not box_hide:
Pablo Vazquez's avatar
Pablo Vazquez committed
        padding_x = 16
        padding_y = 12
        x0 = max(0, pos_x - padding_x)
        y0 = max(0, pos_y - padding_y)
Pablo Vazquez's avatar
Pablo Vazquez committed
        x1 = pos_x + box_width + mouse_size * MOUSE_RATIO * 1.3 + padding_x
Pablo Vazquez's avatar
Pablo Vazquez committed
        y1 = pos_y + max(mouse_size, font_size * row_count) + padding_y
Bart Crouch's avatar
Bart Crouch committed
        positions = [[x0, y0], [x0, y1], [x1, y1], [x1, y0]]
Pablo Vazquez's avatar
Pablo Vazquez committed
        settings = [[bgl.GL_QUADS, min(0.0, box_color_alpha)], [bgl.GL_LINE_LOOP, min(0.0, box_color_alpha)]]
Bart Crouch's avatar
Bart Crouch committed

        for mode, box_alpha in settings:
            bgl.glEnable(bgl.GL_BLEND)
            bgl.glBegin(mode)
Pablo Vazquez's avatar
Pablo Vazquez committed
            bgl.glColor4f(box_color_r, box_color_g, box_color_b, box_color_alpha)
Campbell Barton's avatar
Campbell Barton committed
            for v1, v2 in positions:
Bart Crouch's avatar
Bart Crouch committed
                bgl.glVertex2f(v1, v2)
            bgl.glEnd()
Pablo Vazquez's avatar
Pablo Vazquez committed
    if sc.screencast_keys_show_operator:
        draw_last_operator(context, pos_x, pos_y)

Pablo Vazquez's avatar
Pablo Vazquez committed
    if sc.screencast_keys_timer_show:
        draw_timer(context, pos_x, pos_y)

Pablo Vazquez's avatar
Pablo Vazquez committed
    # get rid of status texts that aren't displayed anymore
    self.key = self.key[:final]
    self.time = self.time[:final]


def draw_callback_px(self, context):
    draw_callback_px_text(self, context)
    draw_callback_px_box(self, context)


Pablo Vazquez's avatar
Pablo Vazquez committed
def draw_last_operator(context, pos_x, pos_y):

    wm = context.window_manager
    sc = context.scene
    font_color_r, font_color_g, font_color_b, font_color_alpha = sc.screencast_keys_text_color
    pos_x, pos_y = getDisplayLocation(context)

Pablo Vazquez's avatar
Pablo Vazquez committed
    if wm.operators:
        last_operator = wm.operators[-1].bl_label
Pablo Vazquez's avatar
Pablo Vazquez committed
        blf.enable(0, blf.SHADOW)
        blf.shadow_offset(0, 1, -1)
        blf.shadow(0, 5, 0.0, 0.0, 0.0, 0.8)
        blf.size(0, sc.screencast_keys_font_size, 36)
        blf.position(0, pos_x - 14, pos_y - 30, 0)
        bgl.glColor4f(font_color_r, font_color_g, font_color_b, font_color_alpha * 0.8)
        blf.draw(0, "Last: %s" % (last_operator))
        blf.disable(0, blf.SHADOW)
Pablo Vazquez's avatar
Pablo Vazquez committed
def draw_timer(context, pos_x, pos_y):

Pablo Vazquez's avatar
Pablo Vazquez committed
    sc = context.scene
Pablo Vazquez's avatar
Pablo Vazquez committed
    #calculate overall time
    overall_time = datetime.timedelta(seconds=int(time.time() - ScreencastKeysStatus.overall_time[0]))

    timer_color_r, timer_color_g, timer_color_b, timer_color_alpha = sc.screencast_keys_timer_color
Pablo Vazquez's avatar
Pablo Vazquez committed
    pos_x = context.region.width - (sc.screencast_keys_timer_size * 12) + 12
Pablo Vazquez's avatar
Pablo Vazquez committed
    pos_y = 10

Pablo Vazquez's avatar
Pablo Vazquez committed
    #draw time
Pablo Vazquez's avatar
Pablo Vazquez committed
    blf.size(0, sc.screencast_keys_timer_size, 72)
    blf.position(0, pos_x, pos_y, 0)
    bgl.glColor4f(timer_color_r, timer_color_g, timer_color_b, timer_color_alpha)
    blf.draw(0, "Elapsed Time: %s" % (overall_time))
def draw_mouse(context, shape, style, alpha):
    # shape and position
    sc   = context.scene
    mouse_size = sc.screencast_keys_mouse_size
    font_size  = sc.screencast_keys_font_size
Pablo Vazquez's avatar
Pablo Vazquez committed
    box_draw = sc.screencast_keys_box_draw

    pos_x, pos_y = getDisplayLocation(context)
Pablo Vazquez's avatar
Pablo Vazquez committed

    if sc.screencast_keys_mouse_position == 'left':
        offset_x = pos_x
Pablo Vazquez's avatar
Pablo Vazquez committed
    if sc.screencast_keys_mouse_position == 'right':
        offset_x = context.region.width - pos_x - (mouse_size * MOUSE_RATIO)

    offset_y = pos_y
    if font_size > mouse_size:
        offset_y += (font_size - mouse_size) / 2
Bart Crouch's avatar
Bart Crouch committed

    bgl.glTranslatef(offset_x, offset_y, 0)
Pablo Vazquez's avatar
Pablo Vazquez committed
    r, g, b, a = sc.screencast_keys_text_color
Bart Crouch's avatar
Bart Crouch committed
    bgl.glColor4f(r, g, b, alpha)

    # inner shape for filled style
    if style == "filled":
        inner_shape = []
        for i in shape_data:
            inner_shape.append(i[0])
Bart Crouch's avatar
Bart Crouch committed

    # outer shape
    for i in shape_data:
        shape_segment = i
        shape_segment[0] = [mouse_size * k for k in shape_segment[0]]
        shape_segment[1] = [mouse_size * k for k in shape_segment[1]]
        shape_segment[2] = [mouse_size * k for k in shape_segment[2]]
        shape_segment[3] = [mouse_size * k for k in shape_segment[3]]
Bart Crouch's avatar
Bart Crouch committed

        # create the buffer
        shape_buffer = bgl.Buffer(bgl.GL_FLOAT, [4, 3], shape_segment)
Bart Crouch's avatar
Bart Crouch committed

        # create the map and draw the triangle fan
        bgl.glMap1f(bgl.GL_MAP1_VERTEX_3, 0.0, 1.0, 3, 4, shape_buffer)
        bgl.glEnable(bgl.GL_MAP1_VERTEX_3)
        if style == "outline":
            bgl.glBegin(bgl.GL_LINE_STRIP)
        else: # style == "filled"
            bgl.glBegin(bgl.GL_TRIANGLE_FAN)
        for j in range(10):
            bgl.glEvalCoord1f(j / 10.0)
        x, y, z = shape_segment[3]
Bart Crouch's avatar
Bart Crouch committed

        # make sure the last vertex is indeed the last one, to avoid gaps
        bgl.glVertex3f(x, y, z)
        bgl.glEnd()
        bgl.glDisable(bgl.GL_MAP1_VERTEX_3)
Bart Crouch's avatar
Bart Crouch committed

    # draw interior
    if style == "filled":
        bgl.glBegin(bgl.GL_TRIANGLE_FAN)
        for i in inner_shape:
            j = [mouse_size * k for k in i]
            x, y, z = j
            bgl.glVertex3f(x, y, z)
        bgl.glEnd()
Bart Crouch's avatar
Bart Crouch committed

    bgl.glTranslatef(-offset_x, -offset_y, 0)

# hardcoded data to draw the graphical represenation of the mouse
def get_shape_data(shape):
    data = []
    if shape == "mouse":
Pablo Vazquez's avatar
Pablo Vazquez committed
        data = [[[0.404, 0.032, 0.0],
Campbell Barton's avatar
Campbell Barton committed
            [0.096, 0.002, 0.0],
Pablo Vazquez's avatar
Pablo Vazquez committed
            [0.059, 0.126, 0.0],
            [0.04, 0.213, 0.0]],
            [[0.04, 0.213, 0.0],
            [-0.015, 0.465, 0.0],
            [-0.005, 0.564, 0.0],
Campbell Barton's avatar
Campbell Barton committed
            [0.032, 0.87, 0.0]],
            [[0.032, 0.87, 0.0],
            [0.05, 0.973, 0.0],
            [0.16, 1.002, 0.0],
            [0.264, 1.002, 0.0]],
            [[0.264, 1.002, 0.0],
            [0.369, 1.002, 0.0],
            [0.478, 0.973, 0.0],
            [0.497, 0.87, 0.0]],
            [[0.497, 0.87, 0.0],
Pablo Vazquez's avatar
Pablo Vazquez committed
            [0.533, 0.564, 0.0],
            [0.554, 0.465, 0.0],
            [0.499, 0.213, 0.0]],
            [[0.499, 0.213, 0.0],
            [0.490, 0.126, 0.0],
Campbell Barton's avatar
Campbell Barton committed
            [0.432, 0.002, 0.0],
Pablo Vazquez's avatar
Pablo Vazquez committed
            [0.404, 0.032, 0.0]]]
Campbell Barton's avatar
Campbell Barton committed
        data = [[[0.154, 0.763, 0.0],
            [0.126, 0.755, 0.0],
            [0.12, 0.754, 0.0],
            [0.066, 0.751, 0.0]],
            [[0.066, 0.751, 0.0],
            [0.043, 0.75, 0.0],
            [0.039, 0.757, 0.0],
            [0.039, 0.767, 0.0]],
            [[0.039, 0.767, 0.0],
            [0.047, 0.908, 0.0],
            [0.078, 0.943, 0.0],
            [0.155, 0.97, 0.0]],
            [[0.155, 0.97, 0.0],
            [0.174, 0.977, 0.0],
            [0.187, 0.975, 0.0],
            [0.191, 0.972, 0.0]],
            [[0.191, 0.972, 0.0],
            [0.203, 0.958, 0.0],
            [0.205, 0.949, 0.0],
            [0.199, 0.852, 0.0]],
            [[0.199, 0.852, 0.0],
            [0.195, 0.77, 0.0],
            [0.18, 0.771, 0.0],
            [0.154, 0.763, 0.0]]]
    elif shape == "middle_button":
Campbell Barton's avatar
Campbell Barton committed
        data = [[[0.301, 0.8, 0.0],
            [0.298, 0.768, 0.0],
            [0.231, 0.768, 0.0],
            [0.228, 0.8, 0.0]],
            [[0.228, 0.8, 0.0],
            [0.226, 0.817, 0.0],
            [0.225, 0.833, 0.0],
            [0.224, 0.85, 0.0]],
            [[0.224, 0.85, 0.0],
            [0.222, 0.873, 0.0],
            [0.222, 0.877, 0.0],
            [0.224, 0.9, 0.0]],
            [[0.224, 0.9, 0.0],
            [0.225, 0.917, 0.0],
            [0.226, 0.933, 0.0],
            [0.228, 0.95, 0.0]],
            [[0.228, 0.95, 0.0],
            [0.231, 0.982, 0.0],
            [0.298, 0.982, 0.0],
            [0.301, 0.95, 0.0]],
            [[0.301, 0.95, 0.0],
            [0.302, 0.933, 0.0],
            [0.303, 0.917, 0.0],
            [0.305, 0.9, 0.0]],
            [[0.305, 0.9, 0.0],
            [0.307, 0.877, 0.0],
            [0.307, 0.873, 0.0],
            [0.305, 0.85, 0.0]],
            [[0.305, 0.85, 0.0],
            [0.303, 0.833, 0.0],
            [0.302, 0.817, 0.0],
Bart Crouch's avatar
Bart Crouch committed
    elif shape == "middle_down_button":
Campbell Barton's avatar
Campbell Barton committed
        data = [[[0.301, 0.8, 0.0],
            [0.298, 0.768, 0.0],
            [0.231, 0.768, 0.0],
            [0.228, 0.8, 0.0]],
            [[0.228, 0.8, 0.0],
            [0.226, 0.817, 0.0],
            [0.225, 0.833, 0.0],
            [0.224, 0.85, 0.0]],
            [[0.224, 0.85, 0.0],
            [0.264, 0.873, 0.0],
            [0.284, 0.873, 0.0],
            [0.305, 0.85, 0.0]],
            [[0.305, 0.85, 0.0],
            [0.303, 0.833, 0.0],
            [0.302, 0.817, 0.0],
Bart Crouch's avatar
Bart Crouch committed
            [0.301, 0.8, 0.0]]]
    elif shape == "middle_up_button":
Campbell Barton's avatar
Campbell Barton committed
        data = [[[0.270, 0.873, 0.0],
            [0.264, 0.873, 0.0],
            [0.222, 0.877, 0.0],
            [0.224, 0.9, 0.0]],
            [[0.224, 0.9, 0.0],
            [0.225, 0.917, 0.0],
            [0.226, 0.933, 0.0],
            [0.228, 0.95, 0.0]],
            [[0.228, 0.95, 0.0],
            [0.231, 0.982, 0.0],
            [0.298, 0.982, 0.0],
            [0.301, 0.95, 0.0]],
            [[0.301, 0.95, 0.0],
            [0.302, 0.933, 0.0],
            [0.303, 0.917, 0.0],
            [0.305, 0.9, 0.0]],
            [[0.305, 0.9, 0.0],
            [0.307, 0.877, 0.0],
            [0.284, 0.873, 0.0],
Bart Crouch's avatar
Bart Crouch committed
            [0.270, 0.873, 0.0]]]
Campbell Barton's avatar
Campbell Barton committed
        data = [[[0.375, 0.763, 0.0],
            [0.402, 0.755, 0.0],
            [0.408, 0.754, 0.0],
            [0.462, 0.751, 0.0]],
            [[0.462, 0.751, 0.0],
            [0.486, 0.75, 0.0],
            [0.49, 0.757, 0.0],
            [0.489, 0.767, 0.0]],
            [[0.489, 0.767, 0.0],
            [0.481, 0.908, 0.0],
            [0.451, 0.943, 0.0],
            [0.374, 0.97, 0.0]],
            [[0.374, 0.97, 0.0],
            [0.354, 0.977, 0.0],
            [0.341, 0.975, 0.0],
            [0.338, 0.972, 0.0]],
            [[0.338, 0.972, 0.0],
            [0.325, 0.958, 0.0],
            [0.324, 0.949, 0.0],
            [0.329, 0.852, 0.0]],
            [[0.329, 0.852, 0.0],
            [0.334, 0.77, 0.0],
            [0.348, 0.771, 0.0],
Bart Crouch's avatar
Bart Crouch committed

    return(data)


# return the shape that belongs to the given event
def map_mouse_event(event):
    shape = False
Bart Crouch's avatar
Bart Crouch committed

    if event == 'LEFTMOUSE':
        shape = "left_button"
    elif event == 'MIDDLEMOUSE':
        shape = "middle_button"
    elif event == 'RIGHTMOUSE':
        shape = "right_button"
Bart Crouch's avatar
Bart Crouch committed
    elif event == 'WHEELDOWNMOUSE':
        shape = "middle_down_button"
    elif event == 'WHEELUPMOUSE':
        shape = "middle_up_button"

    return(shape)

class ScreencastKeysStatus(bpy.types.Operator):
    bl_idname = "view3d.screencast_keys"
Pablo Vazquez's avatar
Pablo Vazquez committed
    bl_label = "Screencast Keys"
    bl_description = "Display keys pressed in the 3D View"
    last_activity = 'NONE'

Bart Crouch's avatar
Bart Crouch committed
    _timer = None

    def modal(self, context, event):
        if context.area:
            context.area.tag_redraw()
Campbell Barton's avatar
Campbell Barton committed

Bart Crouch's avatar
Bart Crouch committed
        if event.type == 'TIMER':
            # no input, so no need to change the display
            return {'PASS_THROUGH'}
Campbell Barton's avatar
Campbell Barton committed

Bart Crouch's avatar
Bart Crouch committed
        scene = context.scene
Pablo Vazquez's avatar
Pablo Vazquez committed
        # keys that shouldn't show up in the 3D View
        mouse_keys = ['MOUSEMOVE','MIDDLEMOUSE','LEFTMOUSE',
         'RIGHTMOUSE', 'WHEELDOWNMOUSE','WHEELUPMOUSE']
        ignore_keys = ['LEFT_SHIFT', 'RIGHT_SHIFT', 'LEFT_ALT',
         'RIGHT_ALT', 'LEFT_CTRL', 'RIGHT_CTRL', 'TIMER']
Bart Crouch's avatar
Bart Crouch committed
        if scene.screencast_keys_mouse != 'text':
Bart Crouch's avatar
Bart Crouch committed
        if event.value == 'PRESS' or (event.value == 'RELEASE' and \
        self.last_activity == 'KEYBOARD' and event.type in mouse_keys):
            # add key-press to display-list
            sc_keys = []
            if event.ctrl:
                sc_keys.append("Ctrl ")
            if event.alt:
                sc_keys.append("Alt ")
            if event.shift:
                sc_keys.append("Shift ")
Bart Crouch's avatar
Bart Crouch committed

                #print("Is a key")
                if event.type not in ignore_keys and event.type in self.key[0]:
                    mods = "+ ".join(sc_keys)
                    old_mods = "+ ".join(self.key[0].split("+ ")[:-1])
                    if mods == old_mods:
                        amount = self.key[0].split(" x")
                        if len(amount) >= 2:
                            sc_amount = " x" + str(int(amount[-1]) + 1)
                        else:
                            sc_amount = " x2"
                        del self.key[0]
                        del self.time[0]
Bart Crouch's avatar
Bart Crouch committed

                #print("Recorded as key")
                sc_keys.append(event.type)
                self.key.insert(0, "+ ".join(sc_keys) + sc_amount)
                self.time.insert(0, time.time())
Bart Crouch's avatar
Bart Crouch committed

            elif event.type in mouse_keys and \
            scene.screencast_keys_mouse == 'icon':
                #print("Recorded as mouse press")
Campbell Barton's avatar
Campbell Barton committed
                self.mouse_time.insert(0, time.time())
            if event.type in mouse_keys:
                self.last_activity = 'MOUSE'
            else:
                self.last_activity = 'KEYBOARD'
            #print("Last activity set to:", self.last_activity)
Bart Crouch's avatar
Bart Crouch committed

        if not context.window_manager.screencast_keys_keys:
Bart Crouch's avatar
Bart Crouch committed
            context.window_manager.event_timer_remove(self._timer)
            context.region.callback_remove(self._handle)
        if context.window_manager.screencast_keys_keys:
Bart Crouch's avatar
Bart Crouch committed
            context.window_manager.event_timer_remove(self._timer)
            context.region.callback_remove(self._handle)
            context.window_manager.screencast_keys_keys = False
    def invoke(self, context, event):
        if context.area.type == 'VIEW_3D':
Pablo Vazquez's avatar
Pablo Vazquez committed
            if context.window_manager.screencast_keys_keys is False:
                # operator is called for the first time, start everything
                context.window_manager.screencast_keys_keys = True
                self.key = []
                self.time = []
                self.mouse = []
                self.mouse_time = []
Pablo Vazquez's avatar
Pablo Vazquez committed
                ScreencastKeysStatus.overall_time = []
                self._handle = context.region.callback_add(draw_callback_px,
Pablo Vazquez's avatar
Pablo Vazquez committed
                self._timer = context.window_manager.event_timer_add(0.075,
Bart Crouch's avatar
Bart Crouch committed
                    context.window)
Pablo Vazquez's avatar
Pablo Vazquez committed
                ScreencastKeysStatus.overall_time.insert(0, time.time())
                context.window_manager.modal_handler_add(self)
                return {'RUNNING_MODAL'}
            else:
                # operator is called again, stop displaying
                context.window_manager.screencast_keys_keys = False
                self.key = []
                self.time = []
                self.mouse = []
                self.mouse_time = []
Pablo Vazquez's avatar
Pablo Vazquez committed
                ScreencastKeysStatus.overall_time = []
Pablo Vazquez's avatar
Pablo Vazquez committed
            self.report({'WARNING'}, "3D View not found, can't run Screencast Keys")
Pablo Vazquez's avatar
Pablo Vazquez committed
class ScreencastKeysTimerReset(bpy.types.Operator):
    """Reset Timer"""
    bl_idname = "view3d.screencast_keys_timer_reset"
    bl_label = "Reset Timer"
    bl_description = "Set the timer back to zero"

    def execute(self, context):
        ScreencastKeysStatus.overall_time = [time.time()]
        return {'FINISHED'}


# properties used by the script
def init_properties():
Bart Crouch's avatar
Bart Crouch committed
    scene = bpy.types.Scene
    wm = bpy.types.WindowManager

Bart Crouch's avatar
Bart Crouch committed
    scene.screencast_keys_pos_x = bpy.props.IntProperty(
Pablo Vazquez's avatar
Pablo Vazquez committed
        name="Position X",
        description="Margin on the X axis",
        default=3,
Bart Crouch's avatar
Bart Crouch committed
        min=0,
        max=100)
    scene.screencast_keys_pos_y = bpy.props.IntProperty(
Pablo Vazquez's avatar
Pablo Vazquez committed
        name="Position Y",
        description="Margin on the Y axis",
Pablo Vazquez's avatar
Pablo Vazquez committed
        default=10,
Bart Crouch's avatar
Bart Crouch committed
        min=0,
        max=100)
    scene.screencast_keys_font_size = bpy.props.IntProperty(
Pablo Vazquez's avatar
Pablo Vazquez committed
        name="Text Size",
        description="Text size displayed on 3D View",
        default=24, min=10, max=150)
Bart Crouch's avatar
Bart Crouch committed
    scene.screencast_keys_mouse_size = bpy.props.IntProperty(
Pablo Vazquez's avatar
Pablo Vazquez committed
        name="Mouse Size",
        description="Mouse size displayed on 3D View",
        default=33, min=10, max=150)
    scene.screencast_keys_text_color = bpy.props.FloatVectorProperty(
        name="Text / Icon Color",
        description="Color for the text and mouse icon",
        default=(1.0, 1.0, 1.0, 1.0),
        min=0.1,
        max=1,
        subtype='COLOR',
        size=4)
    scene.screencast_keys_box_color = bpy.props.FloatVectorProperty(
        name="Box Color",
        description="Box color",
        default=(0.0, 0.0, 0.0, 0.3),
Bart Crouch's avatar
Bart Crouch committed
        min=0,
        max=1,
Pablo Vazquez's avatar
Pablo Vazquez committed
        subtype='COLOR',
        size=4)
    scene.screencast_keys_box_width = bpy.props.IntProperty(
        name="Box Width",
        description="Box default width (resizes with text if needed)",
Pablo Vazquez's avatar
Pablo Vazquez committed
        default = 0,
        min = 0,
        max = 2048,
        soft_max = 1024)
Bart Crouch's avatar
Bart Crouch committed
    scene.screencast_keys_mouse = bpy.props.EnumProperty(
Pablo Vazquez's avatar
Pablo Vazquez committed
        items=(("none", "No Mouse", "Don't display mouse events"),
              ("icon", "Icon", "Display graphical representation of "\
Bart Crouch's avatar
Bart Crouch committed
               "the mouse"),
              ("text", "Text", "Display mouse events as text lines")),
Pablo Vazquez's avatar
Pablo Vazquez committed
        name="Mouse Display",
Bart Crouch's avatar
Bart Crouch committed
        description="Display mouse events",
Pablo Vazquez's avatar
Pablo Vazquez committed
        default='icon')
Pablo Vazquez's avatar
Pablo Vazquez committed
    scene.screencast_keys_mouse_position = bpy.props.EnumProperty(
        items=(("left", "Left", "Align to the left"),
              ("right", "Right", "Align to the right")),
        name="Icon Position",
        description="Align the mouse icon on the 3D View",
        default='left')
Pablo Vazquez's avatar
Pablo Vazquez committed
    scene.screencast_keys_box_draw = bpy.props.BoolProperty(
Pablo Vazquez's avatar
Pablo Vazquez committed
        name="Display Box",
        description = "Display a bounding box behind the text",
Pablo Vazquez's avatar
Pablo Vazquez committed
        default = True)
    scene.screencast_keys_box_hide = bpy.props.BoolProperty(
        name="Hide Box",
        description = "Hide the box when no key is pressed",
Bart Crouch's avatar
Bart Crouch committed
        default = False)
Pablo Vazquez's avatar
Pablo Vazquez committed
    scene.screencast_keys_fade_time = bpy.props.FloatProperty(
        name="Fade Out Time",
        description = "Time in seconds for keys to last on screen",
        default = 3.5,
        min = 0.5,
Pablo Vazquez's avatar
Pablo Vazquez committed
        max = 10.0,
        soft_max = 5.0,
        step = 10,
Pablo Vazquez's avatar
Pablo Vazquez committed
        subtype = 'TIME')
Pablo Vazquez's avatar
Pablo Vazquez committed
    scene.screencast_keys_show_operator = bpy.props.BoolProperty(
        name="Display Last Operator",
        description = "Display the last operator used",
        default = True)
Pablo Vazquez's avatar
Pablo Vazquez committed
    scene.screencast_keys_timer_show = bpy.props.BoolProperty(
        name="Display Timer",
        description = "Counter of the elapsed time in H:MM:SS since the script started",
Pablo Vazquez's avatar
Pablo Vazquez committed
        default = False)
Pablo Vazquez's avatar
Pablo Vazquez committed
    scene.screencast_keys_timer_size = bpy.props.IntProperty(
Pablo Vazquez's avatar
Pablo Vazquez committed
        name="Time Size",
        description="Time size displayed on 3D View",
Pablo Vazquez's avatar
Pablo Vazquez committed
        default=12, min=8, max=100)
    scene.screencast_keys_timer_color = bpy.props.FloatVectorProperty(
Pablo Vazquez's avatar
Pablo Vazquez committed
        name="Time Color",
        description="Color for the time display",
Pablo Vazquez's avatar
Pablo Vazquez committed
        default=(1.0, 1.0, 1.0, 0.3),
        min=0,
        max=1,
        subtype='COLOR',
        size=4)

    # Runstate initially always set to False
    # note: it is not stored in the Scene, but in window manager:
Bart Crouch's avatar
Bart Crouch committed
    wm.screencast_keys_keys = bpy.props.BoolProperty(default=False)


# removal of properties when script is disabled
def clear_properties():
    props = ["screencast_keys_keys", "screencast_keys_mouse",
     "screencast_keys_font_size", "screencast_keys_mouse_size",
Pablo Vazquez's avatar
Pablo Vazquez committed
     "screencast_keys_mouse_position", "screencast_keys_fade_time",
Pablo Vazquez's avatar
Pablo Vazquez committed
     "screencast_keys_pos_x", "screencast_keys_pos_y",
     "screencast_keys_box_draw", "screencast_keys_text_color",
     "screencast_keys_box_color", "screencast_keys_box_hide",
Pablo Vazquez's avatar
Pablo Vazquez committed
     "screencast_keys_box_width", "screencast_keys_show_operator",
     "screencast_keys_timer_show", "screencast_keys_timer_color",
     "screencast_keys_timer_size" ]
    for p in props:
        if bpy.context.window_manager.get(p) != None:
            del bpy.context.window_manager[p]
        try:
            x = getattr(bpy.types.WindowManager, p)
            del x
        except:
            pass


# defining the panel
class OBJECT_PT_keys_status(bpy.types.Panel):
    bl_label = "Screencast Keys"
    bl_space_type = "VIEW_3D"
    bl_region_type = "UI"
Bart Crouch's avatar
Bart Crouch committed

        sc = context.scene
        wm = context.window_manager
Bart Crouch's avatar
Bart Crouch committed

        if not wm.screencast_keys_keys:
Pablo Vazquez's avatar
Pablo Vazquez committed
            layout.operator("view3d.screencast_keys", text="Start Display",
                icon = "PLAY")
Pablo Vazquez's avatar
Pablo Vazquez committed
            layout.operator("view3d.screencast_keys", text="Stop Display",
                icon = "PAUSE")

            split = layout.split()

            col = split.column()
            sub = col.column(align=True)
            sub.label(text="Size:")
            sub.prop(sc, "screencast_keys_font_size", text="Text")
            sub.prop(sc, "screencast_keys_mouse_size", text="Mouse")

            col = split.column()
            sub = col.column(align=True)
            sub.label(text="Position:")
            sub.prop(sc, "screencast_keys_pos_x", text="X")
            sub.prop(sc, "screencast_keys_pos_y", text="Y")

            row = layout.row(align=True)
            row.prop(sc, "screencast_keys_text_color")
            row = layout.row(align=True)
            row.prop(sc, "screencast_keys_fade_time")

            layout.separator()

            row = layout.row(align=True)
Pablo Vazquez's avatar
Pablo Vazquez committed
            row.prop(sc, "screencast_keys_mouse", text="Mouse")
Pablo Vazquez's avatar
Pablo Vazquez committed
            row = layout.row(align=True)
            row.enabled = sc.screencast_keys_mouse == 'icon'
            row.prop(sc, "screencast_keys_mouse_position", expand=True)

Pablo Vazquez's avatar
Pablo Vazquez committed
            layout.label(text="Display:")
Pablo Vazquez's avatar
Pablo Vazquez committed
            row = layout.row(align=True)
Pablo Vazquez's avatar
Pablo Vazquez committed
            row.prop(sc, "screencast_keys_box_draw", text="Box")
Pablo Vazquez's avatar
Pablo Vazquez committed
            row = layout.row(align=True)
            row.active = sc.screencast_keys_box_draw
            row.prop(sc, "screencast_keys_box_color", text="")
            row.prop(sc, "screencast_keys_box_hide", text="Hide")
            row = layout.row(align=True)
            row.active = sc.screencast_keys_box_draw
            row.prop(sc, "screencast_keys_box_width")
            row = layout.row(align=True)
Pablo Vazquez's avatar
Pablo Vazquez committed
            row.prop(sc, "screencast_keys_show_operator", text="Last Operator")
Pablo Vazquez's avatar
Pablo Vazquez committed

            split = layout.split()

            col = split.column()
            sub = col.column(align=True)
            sub.prop(sc, "screencast_keys_timer_show", text="Time")
            col = split.column()
            sub = col.column(align=True)
            sub.active = sc.screencast_keys_timer_show
            sub.prop(sc, "screencast_keys_timer_color", text="")

Pablo Vazquez's avatar
Pablo Vazquez committed
            row = layout.row(align=True)
            row.enabled = sc.screencast_keys_timer_show
Campbell Barton's avatar
Campbell Barton committed
            row.prop(sc, "screencast_keys_timer_size")
Pablo Vazquez's avatar
Pablo Vazquez committed
            row = layout.row(align=True)
            row.enabled = sc.screencast_keys_timer_show
            row.operator("view3d.screencast_keys_timer_reset", text="Reset")
Campbell Barton's avatar
Campbell Barton committed
classes = (ScreencastKeysStatus,
           ScreencastKeysTimerReset,
           OBJECT_PT_keys_status)


def register():
    init_properties()
    for c in classes:
        bpy.utils.register_class(c)

Pablo Vazquez's avatar
Pablo Vazquez committed
    kc = bpy.context.window_manager.keyconfigs.addon
    if kc:
        km = kc.keymaps.new(name='3D View', space_type='VIEW_3D')
        kmi = km.keymap_items.new('view3d.screencast_keys', 'C', 'PRESS', shift=True, alt=True)


def unregister():
    for c in classes:
        bpy.utils.unregister_class(c)
Pablo Vazquez's avatar
Pablo Vazquez committed

    kc = bpy.context.window_manager.keyconfigs.addon
    if kc:
        km = kc.keymaps['3D View']
        for item in km.keymap_items:
            if item.idname == 'view3d.screencast_keys':
                km.keymap_items.remove(item)
                break

Guillermo S. Romero's avatar
Guillermo S. Romero committed
    register()