Source

index / index.py

#!/bin/env python
# -*- coding: utf-8 -*-
"""
Directory browser in Python (OpenGL through pyglet)

Public domain work by anatoly techtonik <techtonik@gmail.com>
Use MIT License if public domain doesn't make sense for you.
"""

import os

import pyglet


# ---[ fonts ]---------------------------------------------------------------
# [x] find/get/load monospace font
#   [ ] bundle (or download) terminus
#   [ ] research other options (style, character sets to draw lines, unicode)
# [x] get font size to initialize window

# [ ] fix font_family as a list under Linux
#   [ ] get real font name for loaded font and store requested name/family
#   [ ] fix have_font under Linux

# Abstract. Font management mechanizm for pyglet. User stories:
# - load font from specific family (monospace)
# - load font by name
# - inspect font is available by family
# - inspect font is available by name
# - get real font name and request real name
# - load system-independent font (bundled with application)

"""
# [ ] TODO: font_family list
# Terminal font by Simon Tatham
# http://www.chiark.greenend.org.uk/~sgtatham/fonts/

font_family = ['Terminus', 'Terminal', 'Courier', 'Monospace']
"""    

font_family = 'Monospace'
render = pyglet.text.Label('A', font_name=font_family)
font_width, font_height = render.content_width, render.content_height

# [ ] pyglet: load font, query size and use it for labels explicitly instead
#             of rendering text to get the metrics
# [ ] bonus: detect how much video memory is available
#   [ ] how much memory is used by the font

# ---[ window ]--------------------------------------------------------------
# [x] set window size to 80x25
#   [ ] try to get coordinate 0 from top left corner instead of bottom left

window = pyglet.window.Window(font_width*80, font_height*25)

# [ ] read filenames
# [ ] print filenames
# [ ] redraw only when there is something to redraw (or F5 pressed)
#   [ ] detect when window is redrawn

from pyglet.window.key import MOTION_UP, MOTION_DOWN, MOTION_LEFT, MOTION_RIGHT
from pyglet.window.key import MOTION_BEGINNING_OF_LINE, MOTION_END_OF_LINE
from pyglet.window.key import MOTION_PREVIOUS_PAGE, MOTION_NEXT_PAGE
# [ ] other motions

# just a convenient container, no instances are meant to be created
class Palette(object):
    foreground = (255, 255, 255, 255)
    background = (0, 0, 0, 0)
    active = (255, 255, 0, 255)
    background_active = (255, 0, 255, 255)

class DNPalette(Palette):
    '''DOS Navigator Palette'''
    background = (85, 85, 85, 255)
    active = (22, 0, 85, 255)

palette = Palette #DNPalette


class FilePanel(object):
    # a list of unique directory entries, one entry is always active
    # [ ] background color (pyglet bug)
    # [x] up/down arrows move cursor
    # [x] scrolling
    # [x] border
    def __init__(self, width, height):
        # file panel dimensions with the border (in symbols)
        self.height = height
        self.width = width
        
        # every item is (filename, label)
        self.items = []
        self.item_idx = 0
        # offset of starting visible element from the items list
        self.offset = 0
        # [ ] resize - correct offset when window height shrinked
        
        # precalculate border labels
        self.border_labels = []
        label = pyglet.text.Label('╔'+ '═'*(self.width-2) + '╗', anchor_y='top',
                    font_name=font_family, x=0, y=font_height*self.height)
        self.border_labels.append(label)
        for x in range(1, self.height-1):
            label = pyglet.text.Label('║' + ' '*(self.width-2) + '║',
                        anchor_y='top', font_name=font_family, x=0, y=font_height*(x+1))
            self.border_labels.append(label)
        label = pyglet.text.Label('╚'+ '═'*(self.width-2) + '╝', anchor_y='top',
                    font_name=font_family, x=0, y=font_height)
        self.border_labels.append(label)

    def _is_item_visible(self, idx):
        """True if item with given idx is visible in panel"""
        return (idx >= self.offset and idx < self.offset + self.height)

    def add_entry(self, name):
        label = pyglet.text.Label(name, font_name=font_family, anchor_y='top')
        idx = len(self.items)  # index that will be assigned to the new entry
        self.items.append((name, label))
        if self._is_item_visible(idx):
            self._refresh_label_positions(idx)
            if idx == self.item_idx:
                self._color_entry(idx, palette.active,
                                       palette.background_active)
        
    def _color_entry(self, idx, foreground, background=None):
        self.items[idx][1].color = foreground
        self.items[idx][1].background_color = background
        
    def _refresh_label_positions(self, idx=None):
        """Change position of visible label on the canvas. If idx is not None,
           refresh coordinate for label with given idx.
        """
        xspace = self.width-2  # size of content area (panel size - border)
        yspace = self.height-2
        xstart = 1  # leave space for border
        ystart = 1
        # TODO [ ] crop right side
        if idx is not None:
            if idx not in range(self.offset, yspace):
                return
            position = ystart + idx - self.offset - 1
            self.items[idx][1].x = xstart * font_width
            self.items[idx][1].y = (ystart + (yspace - position)) * font_height
        else:
            items = self.items[self.offset:self.offset+yspace]
            for position,item in enumerate(items):
                item[1].x = xstart * font_width
                item[1].y = (ystart + (yspace - position)) * font_height
        
    def up(self):
        """Move cursor up"""
        if self.item_idx == 0:
            # stop if first element of list is reached
            return
    
        # repaint old/new active items labels
        self._color_entry(self.item_idx, palette.foreground,
                                         palette.background)
        self.item_idx -= 1
        self._color_entry(self.item_idx, palette.active,
                                         palette.background_active)
        if self.item_idx < self.offset:
            self.offset -= 1
            self._refresh_label_positions()

    def down(self):
        """Move cursor down"""
        yspace = self.height-2
        if self.item_idx == len(filepanel.items)-1:
            # stop if last element of list is reached
            return

        # repaint old/new active items labels                
        self._color_entry(self.item_idx, palette.foreground,
                                         palette.background)
        self.item_idx += 1
        self._color_entry(self.item_idx, palette.active,
                                         palette.background_active)

        if self.item_idx - self.offset == yspace:
            self.offset += 1
            self._refresh_label_positions()
            
    def draw_border(self):
        for label in self.border_labels:
            label.draw()

    def draw(self):
        self.draw_border()
        yspace = self.height-2
        for idx, (name, label) in enumerate(self.items):
            if idx >= self.offset and idx < self.offset + yspace:
                label.draw()
        
    # --- content methods
    def update(self, directory='.'):
        self.title = os.path.abspath(directory)
        for name in os.listdir(directory):
            self.add_entry(name)
        
filepanel = FilePanel(80, 25)

# add handler for on_draw event of the window with a decorator
@window.event
def on_draw():
    # clear to the default black background color
    window.clear()
    filepanel.draw()

@window.event
def on_text_motion(motion):
    if motion == MOTION_UP:
        filepanel.up()
    if motion == MOTION_DOWN:
        filepanel.down()

filepanel.update()
pyglet.app.run()
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.