2013-05-11 21:02:42 +02:00
local base = require ( " radical.base " )
local print = print
local unpack = unpack
local debug = debug
2014-10-06 00:01:54 +02:00
local rawset = rawset
2013-05-16 21:18:55 +02:00
local type = type
2013-05-11 21:02:42 +02:00
local setmetatable = setmetatable
local color = require ( " gears.color " )
local wibox = require ( " wibox " )
local beautiful = require ( " beautiful " )
local cairo = require ( " lgi " ) . cairo
local awful = require ( " awful " )
local util = require ( " awful.util " )
local layout = require ( " radical.layout " )
2013-05-16 21:18:55 +02:00
local checkbox = require ( " radical.widgets.checkbox " )
2013-05-11 21:02:42 +02:00
local arrow_style = require ( " radical.style.arrow " )
2014-02-23 05:59:03 +01:00
local item_mod = require ( " radical.item " )
2014-03-26 21:18:59 +01:00
local glib = require ( " lgi " ) . GLib
2014-10-06 00:01:54 +02:00
local margins2 = require ( " radical.margins " )
2013-05-11 21:02:42 +02:00
2013-11-11 05:18:23 +01:00
local capi , module = { mouse = mouse , screen = screen , keygrabber = keygrabber } , { }
2013-05-11 21:02:42 +02:00
local function get_direction ( data )
local parent_geometry = data.parent_geometry --Local cache to avoid always calling the object hooks
if not parent_geometry or not parent_geometry.drawable then return " bottom " end
local drawable_geom = parent_geometry.drawable . drawable.geometry ( parent_geometry.drawable . drawable )
if parent_geometry.y + parent_geometry.height < drawable_geom.height then --Vertical wibox
if drawable_geom.x > capi.screen [ capi.mouse . screen ] . geometry.width - ( drawable_geom.x + drawable_geom.width ) then
return " right "
else
return " left "
end
else --Horizontal wibox
if drawable_geom.y > capi.screen [ capi.mouse . screen ] . geometry.height - ( drawable_geom.y + drawable_geom.height ) then
return " bottom "
else
return " top "
end
end
end
2014-03-26 21:18:59 +01:00
local function set_geometry_real ( data )
local geo = data._internal . _next_geometry
if geo then
for k , v in pairs ( geo ) do
2015-12-29 11:19:23 +01:00
data.wibox [ k ] = math.ceil ( v )
2014-03-26 21:18:59 +01:00
end
end
data._internal . _next_geometry = nil
data._internal . _need_geometry_reload = nil
end
-- This will update the position before the next loop cycle
-- This avoid tons of round trips with X for dynamic menus
local function change_geometry_idle ( data , x , y , w , h )
data._internal . _next_geometry = data._internal . _next_geometry or { }
local geo = data._internal . _next_geometry
geo.x = x or geo.x
geo.y = y or geo.y
geo.width = w or geo.width
geo.height = h or geo.height
if not data._internal . _need_geometry_reload then
glib.idle_add ( glib.PRIORITY_HIGH_IDLE , function ( ) set_geometry_real ( data ) end )
data._internal . _need_geometry_reload = true
end
end
2013-05-11 21:02:42 +02:00
local function set_position ( self )
2014-01-05 23:35:23 +01:00
if not self.visible then return end
2014-03-26 21:18:59 +01:00
local ret , parent = { x = self.x , y = self.y } , self.parent_geometry
2013-08-07 06:35:49 +02:00
local prefx , prefy = self._internal . private_data.x , self._internal . private_data.y
2013-08-09 07:42:28 +02:00
local src_geo = capi.screen [ capi.mouse . screen ] . geometry
2013-05-11 21:02:42 +02:00
if parent and parent.is_menu then
if parent.direction == " right " then
ret = { x = parent.x - self.width , y = parent.y + ( self.parent_item . y ) }
else
2014-10-06 00:01:54 +02:00
local margins = self.margins
2014-10-18 05:52:26 +02:00
ret = { x = parent.x + parent.width , y = parent.y + ( self.parent_item . y ) - ( margins and self.margins . top or data.default_margins . top or self.style . margins.TOP ) }
2014-02-02 06:35:50 +01:00
--Handle when the menu doesn't fit in the srceen horizontally
if ret.x + self.width > src_geo.x + src_geo.width then
2014-02-19 04:19:38 +01:00
ret.x = parent.x - self.width
2014-02-02 06:35:50 +01:00
end
-- Handle when the menu doesn't fit on the screen vertically
2014-02-19 04:19:38 +01:00
if ret.y + self.height > src_geo.y + src_geo.height then
2014-10-18 05:52:26 +02:00
ret.y = ret.y - self.height + self.item_height + 2 * ( margins and self.margins . top or data.default_margins . top or self.style . margins.TOP )
2013-05-11 21:02:42 +02:00
end
end
elseif parent then
local drawable_geom = parent.drawable . drawable.geometry ( parent.drawable . drawable )
if ( self.direction == " left " ) or ( self.direction == " right " ) then
2014-10-06 00:01:54 +02:00
ret = { x = drawable_geom.x + ( ( self.direction == " right " ) and - self.width or drawable_geom.width ) , y = drawable_geom.y + parent.y --[[+((self.arrow_type ~= base.arrow_type.NONE) and parent.height/2-(self.arrow_x or 20)-6 or 0)]] }
2013-05-11 21:02:42 +02:00
else
2014-06-01 05:45:46 +02:00
ret = { x = drawable_geom.x + parent.x - ( ( self.arrow_type ~= base.arrow_type . NONE ) and ( self.arrow_x or 20 ) + 11 - parent.width / 2 or 0 ) , y = ( self.direction == " bottom " ) and drawable_geom.y - self.height or drawable_geom.y + drawable_geom.height }
2013-05-11 21:02:42 +02:00
end
2013-08-07 06:35:49 +02:00
elseif prefx ~= 0 or prefy ~= 0 then
ret = capi.mouse . coords ( )
if prefx then
ret.x = prefx
end
if prefy then
ret.y = prefy
end
2014-10-06 00:01:54 +02:00
else --Use mouse position to set position --TODO it is called too often
2013-05-11 21:02:42 +02:00
ret = capi.mouse . coords ( )
local draw = awful.mouse . wibox_under_pointer and awful.mouse . wibox_under_pointer ( ) or awful.mouse . drawin_under_pointer and awful.mouse . drawin_under_pointer ( )
if draw then
local geometry = draw.geometry ( draw )
if self.direction == " top " or self.direction == " bottom " then
ret.x = ret.x - ( self.arrow_x or 20 ) - 13
ret.y = geometry.y + geometry.height
2013-08-09 07:42:28 +02:00
if ret.y + self.height > src_geo.height then
2013-05-11 21:02:42 +02:00
self.direction = " bottom "
ret.y = geometry.y - self.height
end
end
end
end
2014-02-02 06:35:50 +01:00
--Handle when menu doesn't fit horizontally (if not handled earlier)
2013-08-09 07:42:28 +02:00
if ret.x + self.width > src_geo.x + src_geo.width then
ret.x = ret.x - ( ret.x + self.width - ( src_geo.x + src_geo.width ) )
elseif ret.x < 0 then
ret.x = 0
end
2014-06-01 05:45:46 +02:00
2014-03-26 21:18:59 +01:00
change_geometry_idle ( self , ret.x , ret.y - 2 * ( self.wibox . border_width or 0 ) )
2013-05-11 21:02:42 +02:00
end
local function setup_drawable ( data )
local internal = data._internal
2014-03-05 06:12:48 +01:00
local private_data = internal.private_data
2013-05-11 21:02:42 +02:00
--Init
internal.w = wibox ( { } )
internal.margin = wibox.layout . margin ( )
if not data.layout then
data.layout = layout.vertical
end
internal.layout = data.layout ( data )
internal.w . visible = false
internal.w . ontop = true
internal.margin : set_widget ( internal.layout )
internal.w : set_widget ( internal.margin )
2013-07-13 07:05:46 +02:00
internal.w : set_fg ( data.fg )
2014-02-18 04:57:04 +01:00
internal.w . opacity = data.opacity
2013-05-11 21:02:42 +02:00
--Getters
2014-03-05 06:12:48 +01:00
data.get_wibox = function ( ) return internal.w end
2014-03-26 21:18:59 +01:00
data.get_x = function ( ) return data._internal . _next_geometry and data._internal . _next_geometry.x or internal.w . x end
data.get_y = function ( ) return data._internal . _next_geometry and data._internal . _next_geometry.y or internal.w . y end
data.get_width = function ( ) return data._internal . _next_geometry and data._internal . _next_geometry.width or internal.w . width end
data.get_height = function ( ) return data._internal . _next_geometry and data._internal . _next_geometry.height or internal.w . height end
2014-03-05 06:12:48 +01:00
data.get_visible = function ( ) return private_data.visible end
data.get_direction = function ( ) return private_data.direction end
data.get_margins = function ( )
2014-10-06 00:01:54 +02:00
if not internal._margins then
local ret = {
2014-10-18 05:52:26 +02:00
left = data.border_width + ( data.default_margins . left or data.style . margins.LEFT or 0 ) ,
right = data.border_width + ( data.default_margins . right or data.style . margins.RIGHT or 0 ) ,
top = data.border_width + ( data.default_margins . top or data.style . margins.TOP or 0 ) ,
bottom = data.border_width + ( data.default_margins . bottom or data.style . margins.BOTTOM or 0 ) ,
2014-10-06 00:01:54 +02:00
}
2014-10-11 09:10:55 +02:00
local m = margins2 ( internal.margin , ret )
2014-10-06 00:01:54 +02:00
rawset ( m , " _reset " , m.reset )
m.reset = function ( margins )
m.defaults = ret
m : _reset ( )
end
internal._margins = m
2013-05-11 21:02:42 +02:00
end
2014-10-06 00:01:54 +02:00
return internal._margins
2013-05-11 21:02:42 +02:00
end
--Setters
2014-03-05 06:12:48 +01:00
data.set_direction = function ( _ , value )
2013-05-11 21:02:42 +02:00
if private_data.direction ~= value and ( value == " top " or value == " bottom " or value == " left " or value == " right " ) then
private_data.direction = value
local fit_w , fit_h = internal.layout : fit ( )
data.height = fit_h
data.width = fit_w
end
end
2014-03-26 21:18:59 +01:00
data.set_x = function ( _ , value ) change_geometry_idle ( data , value ) end
data.set_y = function ( _ , value ) change_geometry_idle ( data , nil , value ) end
2014-03-05 06:12:48 +01:00
data.set_width = function ( _ , value )
2013-05-11 21:02:42 +02:00
local need_update = internal.w . width == ( value + 2 * data.border_width )
local margins = data.margins
2014-03-26 21:18:59 +01:00
change_geometry_idle ( data , nil , nil , value + data.margins . left + data.margins . right )
2013-05-11 21:02:42 +02:00
if need_update then
data.style ( data )
end
end
2014-03-05 06:12:48 +01:00
data.set_height = function ( _ , value )
2013-05-11 21:02:42 +02:00
local margins = data.margins
local need_update = ( internal.w . height ~= ( value + margins.top + margins.bottom ) )
2013-11-11 05:18:23 +01:00
local new_height = ( value + margins.top + margins.bottom ) or 1
2014-03-26 21:18:59 +01:00
change_geometry_idle ( data , nil , nil , nil , new_height > 0 and new_height or 1 )
2013-05-11 21:02:42 +02:00
if need_update then
data.style ( data )
internal.set_position ( data )
end
end
function internal : set_visible ( value )
internal.w . visible = value
2014-03-23 05:14:25 +01:00
if not value and ( not data.parent_geometry or not data.parent_geometry . is_menu ) then
2013-10-27 00:42:59 +02:00
capi.keygrabber . stop ( )
end
2013-05-11 21:02:42 +02:00
end
2013-10-27 00:42:59 +02:00
2013-07-04 07:20:46 +02:00
if data.visible then
local fit_w , fit_h = data._internal . layout : fit ( )
data.width = fit_w
data.height = fit_h
end
2013-05-11 21:02:42 +02:00
end
2014-01-05 23:35:23 +01:00
local function setup_buttons ( data , item , args )
2013-07-08 00:18:55 +02:00
local buttons = { }
for i = 1 , 10 do
if args [ " button " .. i ] then
2014-02-17 05:55:44 +01:00
buttons [ i ] = args [ " button " .. i ]
2013-07-08 00:18:55 +02:00
end
end
2014-01-04 07:27:52 +01:00
-- Click to open sub_menu
2014-02-06 04:48:26 +01:00
if not buttons [ 1 ] and data.sub_menu_on == base.event . BUTTON1 then
2014-02-23 05:59:03 +01:00
buttons [ 1 ] = function ( ) item_mod.execute_sub_menu ( data , item ) end
2014-01-04 07:27:52 +01:00
end
--Hide on right click
if not buttons [ 3 ] then
2014-02-17 05:55:44 +01:00
buttons [ 3 ] = function ( )
2014-08-08 08:02:37 +02:00
data : hide ( )
2014-02-17 05:55:44 +01:00
end
2013-07-08 00:18:55 +02:00
end
2014-01-04 07:27:52 +01:00
-- Scroll up
2013-07-08 00:18:55 +02:00
if not buttons [ 4 ] then
2014-02-17 05:55:44 +01:00
buttons [ 4 ] = function ( )
2013-07-08 00:18:55 +02:00
data : scroll_up ( )
2014-02-17 05:55:44 +01:00
end
2013-07-08 00:18:55 +02:00
end
2014-01-04 07:27:52 +01:00
-- Scroll down
2013-07-08 00:18:55 +02:00
if not buttons [ 5 ] then
2014-02-17 05:55:44 +01:00
buttons [ 5 ] = function ( )
2013-07-08 00:18:55 +02:00
data : scroll_down ( )
2014-02-17 05:55:44 +01:00
end
2013-07-08 00:18:55 +02:00
end
2014-02-17 05:55:44 +01:00
2014-07-28 05:49:14 +02:00
item : connect_signal ( " button::release " , function ( _m , _i , button_id , mods , geo )
2014-02-17 05:55:44 +01:00
if # mods == 0 and buttons [ button_id ] then
2014-07-28 05:49:14 +02:00
buttons [ button_id ] ( _m , _i , mods , geo )
2014-02-17 05:55:44 +01:00
end
end )
2014-01-05 23:35:23 +01:00
end
local function setup_item ( data , item , args )
-- Layout
local f = ( data._internal . layout.setup_item ) or ( layout.vertical . setup_item )
f ( data._internal . layout , data , item , args )
-- Buttons
setup_buttons ( data , item , args )
-- Tooltip
2014-01-04 22:51:50 +01:00
item.widget : set_tooltip ( item.tooltip )
2013-05-11 21:02:42 +02:00
end
local function new ( args )
local args = args or { }
args.internal = args.internal or { }
args.internal . get_direction = args.internal . get_direction or get_direction
args.internal . set_position = args.internal . set_position or set_position
args.internal . setup_drawable = args.internal . setup_drawable or setup_drawable
args.internal . setup_item = args.internal . setup_item or setup_item
args.style = args.style or arrow_style
local ret = base ( args )
2013-05-16 21:18:55 +02:00
ret : connect_signal ( " clear::menu " , function ( _ , vis )
ret._internal . layout : reset ( )
end )
2013-07-08 00:18:55 +02:00
ret : connect_signal ( " _hidden::changed " , function ( _ , item )
item.widget : emit_signal ( " widget::updated " )
end )
2013-05-11 21:02:42 +02:00
return ret
end
return setmetatable ( module , { __call = function ( _ , ... ) return new ( ... ) end } )
2013-11-11 05:18:23 +01:00
-- kate: space-indent on; indent-width 2; replace-tabs on;