curses練習

pythoncursesの練習を作ってみた。
padとstdscrを混ぜて使って画面が更新されないのに2時間くらいはまる。
cursesデバッグしにくいよ。
いまいち勝手がわからない。

#!/usr/bin/python
# -*- coding:utf-8 -*-

import curses
import locale
import os
# 日本語文字化け対策 
locale.setlocale(locale.LC_ALL, "")


class CursesApp(object):
  __slots__=['stdscr', 'callbacks', 'input']
  def __init__(self, stdscr):
    self.stdscr=stdscr
    curses.noecho()
    curses.cbreak()
    curses.curs_set(False)
    self.stdscr.keypad(1)
    self.callbacks={}
    self.callbacks[ord('q')]=self.quit
    self.x=0
    self.y=0
    (self.h, self.w)=self.stdscr.getmaxyx()

  def finalize(self):
    self.stdscr.keypad(0)

  def loop(self):
    while 1:
      c = self.stdscr.getch()
      if c in self.callbacks:
        if self.callbacks[c]():
          break
      else:
        self.default(c)

  def quit(self):
    return True

  def default(self, c):
    pass


"""
表示領域を指定したpad
"""
class PadList(object):
  __slots__=['top', 'left', 'bottom', 'right', 
  'lines', 'topline', 'selected', 'items', 'pad', 'format']
  def __init__(self, top, left, bottom, right, lines):
    self.top=top
    self.left=left
    self.right=right
    self.bottom=bottom
    self.lines=lines
    self.topline=0
    self.selected=0
    self.items={}
    self.format="%-"+("%d" % (self.right-self.left-1))+"s"
    self.pad=curses.newpad(self.lines, self.right-self.left)

  def setText(self, index, text, attrib=curses.A_NORMAL):
    if index<0: return
    if index>=self.lines: return
    self.items[index]=text
    self.pad.addstr(index, 0, (self.format % text), attrib)
 
  def select(self, index):
    height=self.bottom-self.top
    if index<0: 
      index=0
    if index>=self.lines: 
      index=self.lines-1
    if index<self.topline:
      self.topline=index
    if index>self.topline+height:
      self.topline=index-height

    if self.topline+height>self.lines:
      self.topline=self.lines-height

    if self.selected!=index:
      self.setText(self.selected, self.items[self.selected], curses.A_NORMAL)
      self.selected=index
    self.setText(self.selected, self.items[self.selected], curses.A_REVERSE)
    self.refresh()

  def next(self):
    self.select(self.selected+1)
  def prev(self):
    self.select(self.selected-1)
  def refresh(self):
    self.pad.refresh(self.topline, 0, self.top, self.left, self.bottom, self.right)
  def clear(self):
    self.pad.erase()
    self.refresh()


"""
ファイルシステムを移動するサンプル
"""
class Filer(CursesApp):
  def __init__(self, stdscr, path='/'):
    CursesApp.__init__(self, stdscr)
    self.selected=0
    self.topline=0
    self.format="%-"+("%d" % (self.w-1))+"s"
    self.pad=None
    self.setPath(path)
    # keyboard callbacks
    self.callbacks[ord('j')]=self.down
    self.callbacks[ord('k')]=self.up
    self.callbacks[ord('g')]=self.first
    self.callbacks[ord('G')]=self.last
    self.callbacks[0x0a]=self.enter

  def setPath(self, path):
    self.path=path
    self.children=[]
    if self.path!='/':
      self.children.append('..')
    for child in sorted(os.listdir(path)):
      self.children.append(child)
    # ディレクトリ移動時に表示をクリアする
    if self.pad:
      self.pad.clear()
    self.pad=PadList(0, 0, self.h-1, self.w-1, len(self.children))
    for i, child in enumerate(self.children):
      self.pad.setText(i, child)
    self.pad.select(0)
    self.pad.refresh()

  def down(self):
    self.pad.next()
  def up(self):
    self.pad.prev()
  def first(self):
    self.pad.select(0)
  def last(self):
    self.pad.select(len(self.children)-1)
  def enter(self):
    selected=self.children[self.pad.selected]
    path=os.path.join(self.path, selected)
    path=os.path.abspath(path)
    if os.path.isdir(path):
      self.setPath(path)
      self.pad.select(0)

  def loop(self):
    while 1:
      c = self.pad.pad.getch() # stdscrのgetchだと画面が更新されない
      if c in self.callbacks:
        if self.callbacks[c]():
          break
      else:
        self.default(c)

def main(stdscr):
  app=Filer(stdscr)
  app.loop()
  app.finalize()

if __name__=='__main__':
  curses.wrapper(main)

ファイルシステムを移動するだけで他の機能は特にない。
簡単なファイラーみたいのなら作れるかも。

j
k
g
一番上
G
一番下
enter
ディレクトリの中に入る