Hi,
This is the first version of the TVGuide. The TVGuide uses the new
GridMenu. The TVGuide uses the 'advanced' mode of the GridMenu. This
means the row items can have different width's.
Features that still need to be implemented:
- Different colors for items (eg. green when scheduled)
- Station icons i.s.o. the stations name
- Draw up/down arrows
Know issue:
- When a new epg query is done sometimes the grid view wants to draw the
items before the query is finished. The problem is best seen when going
back in time.
- Todo: Test the row/column swap feature on the TVguide
Another (new) plugin is the ArtistAlbum plugin. This displays the the
different albums of every artists. It also uses the GridMenu but now in
the 'normal' mode.
These are the changes I have done:
New files:
- /ui/src/menu/gridmenu.py
Provides the menu style for the grid
- /ui/src/gui/areas/grid_area.py
Draws the grid area
- /ui/src/audio/plugins/album.py
The ArtistAlbum plugin. Used for testing the normal grid
behavior
- /ui/src/tv/plugins/testguide.py
The new TTVGuide, which uses the GridMenu
Changed files (see grid.diff):
- /ui/src/gui/theme.py
Added theme support for the gridmenu
- ui/src/gui/compat.py
Added the GridMenu
- ui/src/tv/plugins/config.cxml
Added the testGuide plugin
- ui/src/audio/plugins/config.cxml
Added the ArtistAlbum plugin
- ui/src/menu/__init__.py
Added the GridMenu
- ui/share/skins/main/basic.fxd
Added default theme support for the gridmenu
- ui/share/skins/main/blurr.fxd
Added theme settings for the ArtistAlbum plugin and the
testguide plugin
Please check/review the code. I have been working on this for some time
now and I might have become blind on some strange coding. ;)
Best regards,
Joost
# -*- coding: iso-8859-1 -*-
# -----------------------------------------------------------------------------
# grid_area.py - A grid area for the Freevo skin
# -----------------------------------------------------------------------------
# $Id: grid_area.py 9536 2007-05-01 11:35:34Z dmeyer $
#
# This module include the GridArea used in the area code for drawing the
# grid areas for menus. It inherits from Area (area.py) and the update
# function will be called to update this area.
#
# -----------------------------------------------------------------------------
# Freevo - A Home Theater PC framework
# Copyright (C) 2002-2005 Krister Lagerstrom, Dirk Meyer, et al.
#
# Maintainer: Dirk Meyer <[EMAIL PROTECTED]>
#
# Please see the file doc/CREDITS for a complete list of authors.
#
# 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 MER-
# CHANTABILITY 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.,
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# -----------------------------------------------------------------------------
__all__ = [ 'GridArea' ]
# python imports
import copy
import os
import math
import time
import array
# kaa imports
from kaa.notifier import Timer
# gui import
from area import Area
from freevo.ui.gui.widgets import Image, Rectangle
import logging
log = logging.getLogger('gui')
from freevo.ui import config
from freevo.ui.gui import imagelib
class _Geometry(object):
"""
Simple object with x, y, with, height values
"""
def __init__(self, x, y, width, height):
self.x = x
self.y = y
self.width = width
self.height = height
self.active_col = 0
self.active_row = 0
def __str__(self):
return '_Geometry: %s,%s %sx%s' % \
(self.x, self.y, self.width, self.height)
class GridArea(Area):
"""
This class defines the GridArea to draw menu grids for the area
part of the gui code.
"""
def __init__(self):
"""
Create the Area and define some needed variables
"""
Area.__init__(self, 'grid')
self.col_width = 0
self.col_height = 0
self.cols = 1
self.row_width = 0
self.row_height = 0
self.rows = 1
self.row_val = None
self.column_val = None
self.selected_val = None
self.default_val = None
# objects on the area
self.row_obj = []
self.col_obj = []
self.up_arrow = None
self.down_arrow = None
self.objects = []
self.background = None
def __calc_items_geometry(self):
# get the settings
settings = self.settings
# get all values for the different types
self.row_val = settings.types['row']
self.column_val = settings.types['column']
self.selected_val = settings.types['selected']
self.default_val = settings.types['default']
# get max font height
max_font_h = max(self.selected_val.font.height, self.default_val.font.height,
self.row_val.font.height)
# get Row width
self.row_width = self.row_val.width
# get col height
self.col_height = self.column_val.font.height
if self.column_val.rectangle:
r = self.column_val.rectangle.calculate(20, self.column_val.font.height)[2]
self.col_height = max(self.col_height, r.height + settings.spacing)
settings_y = settings.y + r.height + settings.spacing
else:
settings_y = settings.y + self.column_val.font.height + settings.spacing
# get Col width
self.col_width = self.column_val.width
self.cols = (settings.width-self.row_width) / self.col_width
# get item height
self.row_height = max_font_h
for val in (self.row_val, self.default_val, self.selected_val):
if val.rectangle:
r = val.rectangle.calculate(20, val.font.height)[2]
self.row_height = max(self.row_height, r.height + settings.spacing)
self.rows = (self.settings.height / self.row_height)-1
def __fit_in_rect(self, rectangle, width, height, font_h):
"""
calculates the rectangle geometry and fits it into the area
"""
x = 0
y = 0
r = rectangle.calculate(width, font_h)[2]
if r.width > width:
r.width, width = width, width - (r.width - width)
if r.height > height:
r.height, height = height, height - (r.height - height)
if r.x < 0:
r.x, x = 0, -r.x
width -= x
if r.y < 0:
r.y, y = 0, -r.y
height -= y
return _Geometry(x, y, width, height), r
def clear(self):
for o in self.objects + self.row_obj + self.col_obj:
if o:
o.unparent()
if self.background:
self.background.unparent()
self.background = None
self.objects = []
self.row_obj = []
self.col_obj = []
def __draw_col_titles(self, settings, x, y, cols, height):
"""
Draws the column titles
"""
if self.col_obj:
return
for o in self.col_obj:
if o:
o.unparent()
col_size = self.col_width
if self.column_val.rectangle:
rect = self.column_val.rectangle.calculate(col_size, height)[2]
for i in range( cols ):
str = self.menu.get_column_name(i)
self.__draw_item(str, x, y, col_size, height, self.column_val, self.col_obj)
x += col_size
def __draw_row_titles(self, settings, x, y, rows, width, height):
"""
Draws the row titles
"""
for o in self.row_obj:
if o:
o.unparent()
self.row_obj = []
for i in range( rows ):
str = self.menu.get_row_name(i)
self.__draw_item(str, x, y, width, height, self.row_val, self.row_obj)
#fix this in the skin
#self.row_obj.append(self.drawbox(tx0, ty0, width, height, r))
y += height
def __draw_item(self, txt, x, y, width, height, type, gui_obj):
"""
Draws an item, depending on the item type, with or without rect
"""
ig = _Geometry(0, 0, width, height)
if type.rectangle:
ig, r = self.__fit_in_rect(type.rectangle, width, height, type.font.height)
gui_obj.append(self.drawbox((x+r.x),
(y+r.y),
r.width+r.size,
height+r.size,
r))
if txt:
string = self.drawstring(txt, type.font,
self.settings, x+ig.x, y+ig.y,
ig.width, ig.height,
align_h=type.align,
align_v ='center')
if string:
gui_obj.append(string)
def update(self):
"""
Update the grid area. This function will be called from Area to
do the real update.
"""
menu = self.menu
settings = self.settings
if menu.update_view:
menu.update_view = False
# layout change, clean everything
self.clear()
self.last_base_col = menu.base_col
self.last_base_row = menu.base_row
#Calculate new settings
self.__calc_items_geometry()
else:
#Todo: only redraw new and previous selected items
# same layout, only clean 'objects'
for o in self.objects:
if o: o.unparent()
self.objects = []
menu.cols = self.cols
menu.rows = self.rows
col_x = settings.x + settings.spacing + self.row_width
col_y = settings.y + settings.spacing
#draw the background
r = _Geometry(0, 0, settings.width, settings.height)
if self.row_val.rectangle:
r = self.row_val.rectangle.calculate( settings.width, settings.height )[ 2 ]
if not self.background:
self.background = self.drawbox( (settings.x + settings.spacing),
(settings.y + settings.spacing),
(((self.cols)*self.col_width + self.row_width))+r.size,
(((self.rows)*self.row_height + self.col_height))+r.size,
r )
# Draw the columns(head)
self.__draw_col_titles(settings, col_x, col_y, self.cols, self.col_height)
row_x = settings.x + settings.spacing
row_y = settings.y + settings.spacing + self.col_height
y0 = row_y
x0 = col_x
# draw the rows(label)
self.__draw_row_titles(settings, row_x, row_y, self.rows, self.row_width,
self.row_height)
#Prepare the left and right arrows
left_arrow_file = None
if settings.images['leftarrow']:
left_arrow = settings.images['leftarrow']
left_arrow_file = left_arrow.filename
right_arrow_file = None
if settings.images['rightarrow']:
right_arrow = settings.images['rightarrow']
right_arrow_file = right_arrow.filename
col_end = col_x+(self.col_width * self.cols)
#Start drawing the items. Draw row after row.
for draw_row in range(self.rows):
try:
x0 = col_x
draw_col = 0
while (x0 < col_end):
item = self.menu.get_item(draw_row, draw_col)
if item != None:
if menu.advanced_mode:
size = item[0]
data = item[1]
width = (self.col_width * size) / 100
else:
data = item
width = self.col_width
val = self.default_val
#is the item selected?
if data == menu.selected:
val = self.selected_val
str = data.name
if x0 == col_x:
# draw left arrow
if ( ( (menu.base_col + draw_col) != 0) or menu.advanced_mode ) and \
left_arrow_file:
y_arrow = y0 + ((self.row_height-left_arrow.height)/2)
x_arrow = col_x
left_arrow_settings = (x_arrow, y_arrow, left_arrow.width, left_arrow.height)
image = self.drawimage(left_arrow_file, left_arrow_settings )
if image:
self.objects.append(image)
#check that we don't write outside the columns
if x0+width > col_end:
width = col_end-x0
#draw the item
self.__draw_item(str, x0, y0, width, self.row_height,
val, self.objects)
x0 += width
else:
#todo check this...
x0 += self.col_width
draw_col += 1
# draw right arrow, Draw right arrow if there are more items to come.
item = self.menu.get_item(draw_row, draw_col)
if item and right_arrow_file:
y_arrow = y0 + ((self.row_height - right_arrow.height)/2)
x_arrow = x0 - right_arrow.width
right_arrow_settings = (x_arrow, y_arrow, right_arrow.width, right_arrow.height)
image = self.drawimage(right_arrow_file, right_arrow_settings )
if image:
self.objects.append(image)
except:
log.exception('grid')
y0 += self.row_height
# draw Up/down arrows
#todo# -*- coding: iso-8859-1 -*-
# -----------------------------------------------------------------------------
# gridmenu.py - a page for the gridmenu stack
# -----------------------------------------------------------------------------
# $Id: gridmenu.py 9541 2007-05-01 18:46:35Z dmeyer $
#
# -----------------------------------------------------------------------------
# Freevo - A Home Theater PC framework
# Copyright (C) 2002 Krister Lagerstrom, 2003-2007 Dirk Meyer, et al.
#
# First Edition: Dirk Meyer <[EMAIL PROTECTED]>
# Maintainer: Dirk Meyer <[EMAIL PROTECTED]>
#
# Please see the file AUTHORS for a complete list of authors.
#
# 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 MER-
# CHANTABILITY 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.,
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# -----------------------------------------------------------------------------
__all__ = [ 'GridMenu' ]
# python imports
import logging
# kaa imports
from kaa.weakref import weakref
# freevo imports
from freevo.ui import config
from freevo.ui.event import *
# menu imports
from item import Item
# get logging object
log = logging.getLogger()
class GridMenu(object):
"""
A Menu page with Items in grid form for the MenuStack.
It is not allowed to change the selected item or the
internal selection directly, use 'select' or 'set_items'
to do this.
The grid is drawn row by row. The advanced_mode of this
menu allowes row items to have different width.
"""
next_id = 0
def __init__( self, heading, grid=[], reload_func = None, type = None ):
self.heading = heading
self.stack = None
# unique id of the menu object
GridMenu.next_id += 1
self.id = GridMenu.next_id
# position in the menu stack
self.pos = -1
# special items for the new skin to use in the view or info
# area. If None, menu.selected will be taken
self.infoitem = None
# Called when a child menu returns. This function returns a new menu
# or None and the old menu will be reused
self.reload_func = reload_func
self.type = type
# Menu type
self._is_submenu = False
# Autoselect menu if it has only one item
self.autoselect = False
self.choices = None
# how many rows and cols does the menu has
# (will be changed by the skin code)
self.cols = 1
self.rows = 1
self.grid = grid
#Define if the contents of the grid should be swapped:
self.col_row_swap = False
#Defines if the advanced mode is used
self.advanced_mode = False
# state, will increase on every item change
self.state = 0
# set items
self.selected = None
self.selected_col = 0
self.selected_row = 0
self.base_col = 0
self.base_row = 0
self.last_base_col = -1
self.last_base_row = -1
self.update_view = True
#self.selected_id = None
def set_items(self, grid=[], selected=None, refresh=True):
"""
Set/replace the items.
"""
# delete ref to menu for old items
for c in self.grid:
for r in c:
if self.advanced_mode:
r[1].menu = None
else:
r.menu = None
# increase state variable
self.state += 1
# set new items and selection
self.grid = grid
# select given item
if selected is not None:
self.select(selected)
# try to reset selection in case we had one
if not self.selected:
# no old selection
if len(self.grid):
self.select(col=0, row=0)
# set menu (self) pointer to the items
sref = weakref(self)
for c in self.grid:
for r in c:
#Check if advanced mode is used
if self.advanced_mode:
r[1].menu = sref
else:
r.menu = sref
if refresh and self.stack:
self.stack.refresh()
def select(self, item=None, col=0, row=0, refresh=True):
"""
Set the selection to a specific item in the list. If item in an int
select a new item relative to current selected
"""
if item:
if isinstance(item, Item):
# select item
found = False
for r in self.grid:
if self.advanced_mode:
for i in r:
if item == i[1]:
#todo: chack
self.selected_col = r.index(i)
self.selected_row = self.grid.index(r)
found = True
self.selected = item
#self.selected_id = self.selected.__id__()
else:
if item in r:
self.selected_col = r.index(item)
self.selected_row = self.grid.index(r)
found = True
self.selected = item
#self.selected_id = self.selected.__id__()
if not found:
log.error('%s not in list', item)
return False
else:
# select relative
if self.col_row_swap:
self.selected_col = min(max(col, 0), len(self.grid)-1 )
self.selected_row = min(max(row, 0), len(self.grid[self.selected_col])-1 )
if self.advanced_mode:
self.selected = self.grid[self.selected_col][self.selected_row][1]
else:
self.selected = self.grid[self.selected_col][self.selected_row]
else:
self.selected_row = min(max(row, 0), len(self.grid)-1 )
self.selected_col = min(max(col, 0), len(self.grid[self.selected_row])-1 )
if self.advanced_mode:
self.selected = self.grid[self.selected_row][self.selected_col][1]
else:
self.selected = self.grid[self.selected_row][self.selected_col]
#self.selected_id = self.selected.__id__()
#Find Which columns/rows to draw, the next update
if self.selected_col-self.base_col > self.cols-1:
self.base_col = self.selected_col - (self.cols-1)
elif self.selected_col-self.base_col < 0:
self.base_col = self.selected_col
if self.selected_row-self.base_row > self.rows-1:
self.base_row = self.selected_row - (self.rows-1)
elif self.selected_row-self.base_row < 0:
self.base_row = self.selected_row
#refresh view?
if (self.last_base_col != self.base_col) or \
(self.last_base_row != self.base_row):
self.update_view = True
return True
def get_items(self):
"""
Return the list of items.
"""
if self.col_row_swap:
items = self.grid[self.base_col+col]
else:
items = self.grid[self.base_row+row]
return items
def get_item(self, row, col):
"""
Return the data for that col, row.
"""
try:
if self.col_row_swap:
item = self.grid[self.base_col+col][self.base_row+row]
else:
item = self.grid[self.base_row+row][self.base_col+col]
except:
return None
return item
def get_selection(self):
"""
Return current selected item.
"""
return self.selected
def get_column_name(self, col):
"""
Return the column name
"""
if self.col_row_swap:
text = "Row %s" % (self.base_row+row)
else:
text = "Column %s" % (self.base_col+col)
return text
def get_row_name(self, row):
"""
Return the row name
"""
if self.col_row_swap:
text = "Column %s" % (self.base_col+col)
else:
text = "Row %s" % (self.base_row+row)
return text
def eventhandler(self, event):
"""
Handle events for this menu page.
"""
if self.grid == None:
return False
if self.cols == 1:
if config.menu.arrow_navigation:
if event == MENU_LEFT:
event = MENU_BACK_ONE_MENU
elif event == MENU_RIGHT:
event = MENU_SELECT
else:
if event == MENU_LEFT:
event = MENU_PAGEUP
elif event == MENU_RIGHT:
event = MENU_PAGEDOWN
if self.rows == 1:
if event == MENU_LEFT:
event = MENU_UP
if event == MENU_RIGHT:
event = MENU_DOWN
if event == MENU_UP:
self.select(col=self.selected_col,
row=self.selected_row-1 )
return True
if event == MENU_DOWN:
self.select(col=self.selected_col,
row=self.selected_row+1 )
return True
if event == MENU_PAGEUP:
self.select(col=self.selected_col,
row=self.selected_row-self.rows )
return True
if event == MENU_PAGEDOWN:
self.select(col=self.selected_col,
row=self.selected_row+self.rows )
return True
if event == MENU_LEFT:
self.select(col=self.selected_col-1,
row=self.selected_row )
return True
if event == MENU_RIGHT:
self.select(col=self.selected_col+1,
row=self.selected_row )
return True
if event == MENU_PLAY_ITEM and hasattr(self.selected, 'play'):
self.selected.play()
return True
if event == MENU_CHANGE_SELECTION:
self.select(event.arg)
return True
if event == MENU_SELECT or event == MENU_PLAY_ITEM:
actions = self.selected._get_actions()
if not actions:
OSD_MESSAGE.post(_('No action defined for this choice!'))
else:
result = actions[0]()
if result:
# action handed this event and returned either True or
# an InProgress object
return result
# in any case, return True because this event is handled here
return True
if event == MENU_SUBMENU:
if self._is_submenu or not self.stack:
return False
items = self.selected.get_submenu()
if len(items) < 2:
# no submenu
return False
s = Menu(self.selected.name, items)
s._is_submenu = True
self.stack.pushmenu(s)
return True
if event == MENU_CALL_ITEM_ACTION:
log.info('calling action %s' % event.arg)
for action in self.selected._get_actions():
if action.shortcut == event.arg:
return action() or True
log.info('action %s not found' % event.arg)
return True
return False
# -*- coding: iso-8859-1 -*-
# -----------------------------------------------------------------------------
# album.py - browse collection based on album/artist tag
# -----------------------------------------------------------------------------
# $Id: album.py 9541 2007-05-01 18:46:35Z dmeyer $
#
# This plugin adds an item 'Browse by Artists albums' to the audio menu.
# It views the albums per artist in a grid view.
#
# This plugin is also a simple example how to write plugins and how to use
# kaa.beacon in freevo.
#
# -----------------------------------------------------------------------------
# Freevo - A Home Theater PC framework
# Copyright (C) 2006 Dirk Meyer, et al.
#
# First Edition: Dirk Meyer <[EMAIL PROTECTED]>
# Maintainer: Dirk Meyer <[EMAIL PROTECTED]>
#
# Please see the file AUTHORS for a complete list of authors.
#
# 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 MER-
# CHANTABILITY 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.,
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# -----------------------------------------------------------------------------
# Kaa imports
import kaa.beacon
from kaa.strutils import str_to_unicode
# Freevo imports
from freevo.ui.mainmenu import MainMenuPlugin
from freevo.ui.menu import Item, ItemList, ActionItem, Menu, Action, GridMenu, MediaItem
from freevo.ui.playlist import Playlist
from freevo.ui.audio import AudioItem
from freevo.ui.directory import DirItem
class AlbumItem(MediaItem):
"""
Item for on Album (or all) for an artist.
"""
def __init__(self, artist, album, parent):
MediaItem.__init__(self, parent)
self.artist = artist
self.album = album
self.name = _('[ All Songs ]')
if album:
self.name = album
def browse(self):
"""
Show all items from that artist.
"""
title = str_to_unicode(self.artist)
if self.album:
query = kaa.beacon.query(artist=self.artist, album=self.album, type='audio')
title = '%s - %s' % (title, str_to_unicode(self.album))
else:
query = kaa.beacon.query(artist=self.artist, type='audio')
# FIXME: monitor query for live update
self.playlist = Playlist(title, query, self, type='audio')
self.playlist.browse()
def actions(self):
"""
Actions for this item.
"""
return [ Action(_('Browse Songs'), self.browse) ]
class ArtistAlbumView(GridMenu):
"""
Item for an artist.
"""
def __init__(self, parent):
GridMenu.__init__(self, _('Artist albums view'), type = 'audio grid')
self.artists_base = 0
self.artists = []
#Query all artists.
for artist in kaa.beacon.query(attr='artist', type='audio'):
self.artists.append(artist)
self.col_row_swap = False
self.update()
def update(self):
"""
update the guide area
"""
items = []
for artist in self.artists:
# FIXME: monitor query for live update
query = kaa.beacon.query(attr='album', artist=artist, type='audio')
albums = [ AlbumItem(artist, None, self) ]
for album in query:
albums.append(AlbumItem( artist, album, self))
items.append(albums)
self.set_items(items)
def get_column_name(self, col):
"""
Return the column name
"""
text = 'Album %s' % (self.base_col+col+1)
return text
def get_row_name(self, row):
"""
Return the row name
"""
artist_name = ''
artist = self.artists[self.base_row+row]
# Work around a beacon bug
for part in artist.split(' '):
artist_name += ' ' + str_to_unicode(part.capitalize())
artist_name = artist_name.strip()
return artist_name
class PluginInterface(MainMenuPlugin):
"""
Add 'Browse by Artist albums' to the audio menu.
"""
def items(self, parent):
return [ ActionItem(_('Browse by Artists albums'), parent, self.show) ]
def show(self, parent):
artistalbumview = ArtistAlbumView(parent)
parent.get_menustack().pushmenu(artistalbumview)
# -*- coding: iso-8859-1 -*-
# -----------------------------------------------------------------------------
# testguide.py - The the Freevo TV Guide
# -----------------------------------------------------------------------------
# $Id: testguide.py 9541 2007-05-01 18:46:35Z dmeyer $
#
# -----------------------------------------------------------------------------
# Freevo - A Home Theater PC framework
# Copyright (C) 2002 Krister Lagerstrom, 2003-2007 Dirk Meyer, et al.
#
# Maintainer: Dirk Meyer <[EMAIL PROTECTED]>
#
# Please see the file AUTHORS for a complete list of authors.
#
# 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 MER-
# CHANTABILITY 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.,
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# -----------------------------------------------------------------------------
# python imports
import os
import sys
import time
import logging
import kaa.epg
import kaa.notifier
# freevo imports
from freevo.ui.event import *
from freevo.ui.mainmenu import MainMenuPlugin
from freevo.ui.menu import Menu, GridMenu, ActionItem
from freevo.ui.tv.program import ProgramItem
from freevo.ui.application import MessageWindow
from freevo.ui import config
# get logging object
log = logging.getLogger('tv')
ONE_HOUR_IN_TIME = (60 * 60)
ONE_DAY_IN_TIME = (24 * ONE_HOUR_IN_TIME)
COLUMN_TIME = ONE_HOUR_IN_TIME
#fix me: Use number of days from config files
MAX_DAYS = 1
class PluginInterface(MainMenuPlugin):
def items(self, parent):
return [ ActionItem(_('TV Guide'), parent, self.show) ]
def show(self, parent):
if not kaa.epg.is_connected():
MessageWindow(_('TVServer not running')).show()
return
guide = TVGuide2(parent)
parent.get_menustack().pushmenu(guide)
class TVGuide2(GridMenu):
"""
TVGuide2 menu.
"""
def __init__(self, parent):
GridMenu.__init__(self, _('TV Guide2'), type = 'tv grid')
self.parent = parent
self.viewed_time = int(time.time())
self.viewed_time = ((self.viewed_time / COLUMN_TIME) * COLUMN_TIME)
self.prev_viewed_time = 0
# current channel is the first one
self.channels = kaa.epg.get_channels(sort=True)
# FIXME: make it work without step()
if isinstance(self.channels, kaa.notifier.InProgress):
while not self.channels.is_finished:
kaa.notifier.step()
self.channels = self.channels()
self.query_start_time = self.viewed_time
self.query_stop_time = 0
self.query_data = []
# current program is the current running
self.advanced_mode = True
self.selected = None
self.selected_start_time = self.viewed_time
self.update()
def get_item(self, row, col):
"""
Return the data for that col, row.
"""
try:
item = self.grid[self.base_row+row][col]
except:
return None
return item
@kaa.notifier.yield_execution()
def update(self):
"""
update the guide information
"""
#Find the new time to display
if self.selected:
self.selected_start_time = self.selected.start
if self.selected_start_time >= (self.viewed_time + (COLUMN_TIME * self.cols)):
#Put halfway the grid
self.viewed_time = self.selected_start_time - (COLUMN_TIME * self.cols / 2)
elif self.selected_start_time < self.viewed_time:
self.viewed_time = self.selected_start_time
self.viewed_time = ((self.viewed_time / COLUMN_TIME) * COLUMN_TIME)
#do we need to start a new query?
if self.viewed_time < self.query_start_time or \
(self.viewed_time + (COLUMN_TIME * self.cols)) > self.query_stop_time:
print 'new query'
self.query_start_time = self.viewed_time
self.query_stop_time = self.query_start_time + ONE_DAY_IN_TIME
self.query_data = []
for channel in self.channels:
programs = []
# query the epg database in background
wait = kaa.epg.search(channel=channel, time=(self.query_start_time, self.query_stop_time))
yield wait
# get data from InProgress object
query_data = wait()
#Sort the programs
query_data.sort(lambda x,y: cmp(x.start, y.start))
for data in query_data:
data = ProgramItem(data, self)
programs.append(data)
self.query_data.append(programs)
#Calculate new view
if self.prev_viewed_time != self.viewed_time:
self.update_view = True
self.prev_viewed_time = self.viewed_time
items = []
for channel in self.query_data:
programs = []
for data in channel:
#only add items which are in the view
if data.stop >= self.viewed_time:
#selected the correct first item
if self.selected == None and \
data.stop > self.viewed_time:
self.selected = data
start = max(data.start, self.viewed_time)
size = ((data.stop - start) *100) / COLUMN_TIME
i = (size , data)
programs.append(i)
items.append(programs)
self.set_items(items, selected=self.selected)
def get_column_name(self, col):
"""
Return the column name
"""
#get rid of the minutes
t = self.viewed_time
t += (col*COLUMN_TIME)
return unicode(time.strftime(config.tv.timeformat,
time.localtime(t)))
def get_row_name(self, row):
"""
Return the row name
"""
return self.channels[self.base_row+row].name
def select_program(self):
"""
Select program for the new row
"""
for program in self.grid[self.selected_row]:
size, data = program
if data.start <= self.selected_start_time and data.stop > self.selected_start_time:
self.select(row=self.selected_row, col=self.grid[self.selected_row].index(program))
def eventhandler(self, event):
handled = False
if not self.selected:
# not ready yet
return True
if event == MENU_CHANGE_STYLE:
handled = True
elif event == MENU_UP:
self.select(col=self.selected_col,
row=self.selected_row-1 )
self.select_program()
handled = True
elif event == MENU_DOWN:
self.select(col=self.selected_col,
row=self.selected_row+1 )
self.select_program()
handled = True
elif event == MENU_LEFT:
self.select(col=self.selected_col-1,
row=self.selected_row )
self.update()
handled = True
elif event == MENU_RIGHT:
self.select(col=self.selected_col+1,
row=self.selected_row )
self.update()
handled = True
elif event == TV_SHOW_CHANNEL:
self.selected.channel_details()
handled = True
elif event == MENU_SUBMENU:
self.selected.submenu(additional_items=True)
handled = True
elif event == TV_START_RECORDING:
# TODO: make this schedule or remove
self.selected.submenu(additional_items=True)
handled = True
elif event == PLAY:
self.selected.watch_channel()
handled = True
elif event == MENU_SELECT or event == PLAY:
# Check if the selected program is >7 min in the future
# if so, bring up the submenu
now = time.time() + (7*60)
if self.selected.start > now:
self.selected.submenu(additional_items=True)
else:
self.selected.watch_channel()
handled = True
else:
#If not handled try default eventhandler
handled = GridMenu.eventhandler(self, event)
return handled
Index: ui/src/gui/theme.py
===================================================================
--- ui/src/gui/theme.py (revision 9911)
+++ ui/src/gui/theme.py (working copy)
@@ -533,12 +533,12 @@
class MenuSet(object):
"""
- the complete menu with the areas screen, title, subtitle, view, listing
- and info in it
+ the complete menu with the areas screen, title, subtitle, view, listing,
+ grid and info in it
"""
def __init__(self):
self.areas = [ 'screen', 'title', 'subtitle', 'view', 'listing',
- 'info', 'progress' ]
+ 'info', 'grid', 'progress' ]
for c in self.areas:
setattr(self, c, Area(c))
@@ -567,7 +567,7 @@
def __init__(self, name, source=None):
XMLData.__init__(self, self.VARS, source)
self.name = name
- if name == 'listing':
+ if name == 'listing' or 'grid':
self.images = {}
if not source:
self.x = -1
@@ -593,7 +593,7 @@
except TypeError:
pass
for subnode in node.children:
- if subnode.name == u'image' and self.name == 'listing':
+ if subnode.name == u'image' and self.name == 'listing' or 'grid':
label = attr_str(subnode, 'label', '')
if label:
if not label in self.images:
@@ -1246,6 +1246,10 @@
for image in s[i].listing.images:
foo = s[i].listing.images[image]
s[i].listing.images[image] = foo.prepare_copy(None, search_dirs, self.__images)
+ if s[i] and hasattr(s[i], 'grid'):
+ for image in s[i].grid.images:
+ foo = s[i].grid.images[image]
+ s[i].grid.images[image] = foo.prepare_copy(None, search_dirs, self.__images)
# menu structures
self.default_menu = {}
@@ -1292,6 +1296,11 @@
for image in sli:
sli[image] = sli[image].prepare_copy(None, search_dirs,
self.__images)
+ if s[i] and hasattr(s[i], 'grid'):
+ sli = s[i].grid.images
+ for image in sli:
+ sli[image] = sli[image].prepare_copy(None, search_dirs,
+ self.__images)
# prepare popup style
self.popup = layout[self.__popup]
Index: ui/src/gui/compat.py
===================================================================
--- ui/src/gui/compat.py (revision 9911)
+++ ui/src/gui/compat.py (working copy)
@@ -52,7 +52,7 @@
class _Menu(BaseApplication):
name = 'menu'
- areas = ('screen', 'title', 'subtitle', 'view', 'listing', 'info')
+ areas = ('screen', 'title', 'subtitle', 'view', 'listing', 'info', 'grid')
def __init__(self):
from freevo.ui.gui.areas import Handler
Index: ui/src/tv/plugins/config.cxml
===================================================================
--- ui/src/tv/plugins/config.cxml (revision 9911)
+++ ui/src/tv/plugins/config.cxml (working copy)
@@ -16,6 +16,10 @@
</var>
</group>
+ <group name="testguide" plugin="15">
+ <desc>Add item to show the test guide</desc>
+ </group>
+
<group name="genre" plugin="30">
<desc>Add item to browse the EPG by genre</desc>
</group>
Index: ui/src/audio/plugins/config.cxml
===================================================================
--- ui/src/audio/plugins/config.cxml (revision 9911)
+++ ui/src/audio/plugins/config.cxml (working copy)
@@ -4,4 +4,9 @@
<group name="artist" plugin="10">
<desc>Show audio files sorted by artist and album</desc>
</group>
+
+ <desc lang="en">audio plugins</desc>
+ <group name="album" plugin="10">
+ <desc>Show albums sorted by artist in a grid view</desc>
+ </group>
</config>
Index: ui/src/menu/__init__.py
===================================================================
--- ui/src/menu/__init__.py (revision 9911)
+++ ui/src/menu/__init__.py (working copy)
@@ -40,5 +40,6 @@
from mediaitem import MediaItem
from action import Action
from menu import Menu
+from gridmenu import GridMenu
from stack import MenuStack
from plugin import ItemPlugin, MediaPlugin
Index: ui/share/skins/main/basic.fxd
===================================================================
--- ui/share/skins/main/basic.fxd (revision 9911)
+++ ui/share/skins/main/basic.fxd (working copy)
@@ -41,7 +41,7 @@
<style text="normal text style"/>
</menu>
- <!-- defualt menu when no item has an image -->
+ <!-- default menu when no item has an image -->
<menu type="default no image">
<style text="default no image"/>
</menu>
@@ -206,6 +206,33 @@
</content>
</layout>
+ <!-- default grid area -->
+ <layout label="grid">
+ <content type="text" spacing="0">
+
+ <item type="row" font="item" width="80">
+ <rectangle bgcolor="0x88000066" size="1" color="0x000000" x="-5" y="-5"
+ width="max+10" height="max+10"/>
+ </item>
+
+ <item type="column" font="item" width="175">
+ <rectangle bgcolor="0x88000066" size="1" color="0x000000" x="-5" y="-5"
+ width="max+10" height="max+10"/>
+ </item>
+
+ <item type="default" font="item">
+ <rectangle bgcolor="0xff000000" size="1" color="0x000000" x="-5"
+ y="-5" width="max+10" height="max+10"/>
+ </item>
+
+ <item type="selected" font="selected">
+ <rectangle bgcolor="selection" size="1" color="0x000000" x="-5"
+ y="-5" width="max+10" height="max+10"/>
+ </item>
+
+ </content>
+ </layout>
+
<!-- font used in this layouts -->
<font label="title area" name="VeraBd.ttf" size="24" color="0xffffff"/>
<font label="subtitle" name="VeraBd.ttf" size="18" color="0xffffff"/>
Index: ui/share/skins/main/blurr.fxd
===================================================================
--- ui/share/skins/main/blurr.fxd (revision 9911)
+++ ui/share/skins/main/blurr.fxd (working copy)
@@ -268,6 +268,45 @@
<info layout="audio info" x="28" y="370" width="273" height="190"/>
</menuset>
+ <menu type="audio grid">
+ <style text="audio grid menu"/>
+ </menu>
+
+ <menuset label="audio grid menu">
+ <screen layout="screen" x="0" y="0" width="800" height="600"/>
+ <title visible="no"/>
+ <grid layout="audio grid" x="10" y="90" width="780" height="500">
+ <image x="765" y="90" width="32" height="32" label="uparrow"
+ filename="up.png"/>
+ <image x="765" y="max-32" width="32" height="32" label="downarrow"
+ filename="down.png"/>
+ <image width="16" height="16" label="leftarrow" filename="left.png"/>
+ <image width="16" height="16" label="rightarrow" filename="right.png"/>
+ </grid>
+ </menuset>
+
+ <!-- audio grid area -->
+ <layout label="audio grid">
+ <content type="text" spacing="0">
+ <item type="row" font="item" width="240">
+ <rectangle bgcolor="0x88000066" size="1" color="0x000000" x="-5" y="-5"
+ width="max+10" height="max+10"/>
+ </item>
+ <item type="column" font="item" width="270">
+ <rectangle bgcolor="0x88000066" size="1" color="0x000000" x="-5" y="-5"
+ width="max+10" height="max+10"/>
+ </item>
+ <item type="default" font="item">
+ <rectangle bgcolor="0xff000000" size="1" color="0x000000" x="-5"
+ y="-5" width="max+10" height="max+10"/>
+ </item>
+ <item type="selected" font="selected">
+ <rectangle bgcolor="selection" size="1" color="0x000000" x="-5"
+ y="-5" width="max+10" height="max+10"/>
+ </item>
+ </content>
+ </layout>
+
<layout label="audio screen">
<background>
<image image="background" x="0" y="0" label="background"/>
@@ -548,6 +587,26 @@
</background>
</layout>
+ <!-- GUIDE2 -->
+
+ <menu type="tv grid">
+ <style text="tv grid menu"/>
+ </menu>
+
+ <menuset label="tv grid menu">
+ <screen layout="screen" x="0" y="0" width="800" height="600"/>
+ <title visible="no"/>
+ <info layout="tv info" x="10" y="90" width="780" height="140"/>
+ <grid layout="grid" x="10" y="240" width="780" height="350">
+ <image x="765" y="240" width="32" height="32" label="uparrow"
+ filename="up.png"/>
+ <image x="765" y="max-32" width="32" height="32" label="downarrow"
+ filename="down.png"/>
+ <image width="16" height="16" label="leftarrow" filename="left.png"/>
+ <image width="16" height="16" label="rightarrow" filename="right.png"/>
+ </grid>
+ </menuset>
+
<!-- GUIDE -->
<menuset label="tv menu">
-------------------------------------------------------------------------
This SF.net email is sponsored by: Microsoft
Defy all challenges. Microsoft(R) Visual Studio 2005.
http://clk.atdmt.com/MRT/go/vse0120000070mrt/direct/01/
_______________________________________________
Freevo-devel mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/freevo-devel