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

Reply via email to