--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/.hgignore Wed Feb 16 23:51:30 2011 +0100
@@ -0,0 +1,3 @@
+.*~
+tuikit/.*\.pyc
+docs/_build
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/Makefile Wed Feb 16 23:51:30 2011 +0100
@@ -0,0 +1,89 @@
+# Makefile for Sphinx documentation
+#
+
+# You can set these variables from the command line.
+SPHINXOPTS =
+SPHINXBUILD = sphinx-build
+PAPER =
+BUILDDIR = _build
+
+# Internal variables.
+PAPEROPT_a4 = -D latex_paper_size=a4
+PAPEROPT_letter = -D latex_paper_size=letter
+ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
+
+.PHONY: help clean html dirhtml pickle json htmlhelp qthelp latex changes linkcheck doctest
+
+help:
+ @echo "Please use \`make <target>' where <target> is one of"
+ @echo " html to make standalone HTML files"
+ @echo " dirhtml to make HTML files named index.html in directories"
+ @echo " pickle to make pickle files"
+ @echo " json to make JSON files"
+ @echo " htmlhelp to make HTML files and a HTML help project"
+ @echo " qthelp to make HTML files and a qthelp project"
+ @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
+ @echo " changes to make an overview of all changed/added/deprecated items"
+ @echo " linkcheck to check all external links for integrity"
+ @echo " doctest to run all doctests embedded in the documentation (if enabled)"
+
+clean:
+ -rm -rf $(BUILDDIR)/*
+
+html:
+ $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
+ @echo
+ @echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
+
+dirhtml:
+ $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
+ @echo
+ @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
+
+pickle:
+ $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
+ @echo
+ @echo "Build finished; now you can process the pickle files."
+
+json:
+ $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
+ @echo
+ @echo "Build finished; now you can process the JSON files."
+
+htmlhelp:
+ $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
+ @echo
+ @echo "Build finished; now you can run HTML Help Workshop with the" \
+ ".hhp project file in $(BUILDDIR)/htmlhelp."
+
+qthelp:
+ $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
+ @echo
+ @echo "Build finished; now you can run "qcollectiongenerator" with the" \
+ ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
+ @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Tuikit.qhcp"
+ @echo "To view the help file:"
+ @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Tuikit.qhc"
+
+latex:
+ $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+ @echo
+ @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
+ @echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \
+ "run these through (pdf)latex."
+
+changes:
+ $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
+ @echo
+ @echo "The overview file is in $(BUILDDIR)/changes."
+
+linkcheck:
+ $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
+ @echo
+ @echo "Link check complete; look for any errors in the above output " \
+ "or in $(BUILDDIR)/linkcheck/output.txt."
+
+doctest:
+ $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
+ @echo "Testing of doctests in the sources finished, look at the " \
+ "results in $(BUILDDIR)/doctest/output.txt."
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/conf.py Wed Feb 16 23:51:30 2011 +0100
@@ -0,0 +1,194 @@
+# -*- coding: utf-8 -*-
+#
+# Tuikit documentation build configuration file, created by
+# sphinx-quickstart on Wed Feb 2 23:35:28 2011.
+#
+# This file is execfile()d with the current directory set to its containing dir.
+#
+# Note that not all possible configuration values are present in this
+# autogenerated file.
+#
+# All configuration values have a default; values that are commented out
+# serve to show the default.
+
+import sys, os
+
+# If extensions (or modules to document with autodoc) are in another directory,
+# add these directories to sys.path here. If the directory is relative to the
+# documentation root, use os.path.abspath to make it absolute, like shown here.
+#sys.path.append(os.path.abspath('.'))
+
+# -- General configuration -----------------------------------------------------
+
+# Add any Sphinx extension module names here, as strings. They can be extensions
+# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
+extensions = ['sphinx.ext.autodoc', 'sphinx.ext.doctest', 'sphinx.ext.todo']
+
+# Add any paths that contain templates here, relative to this directory.
+templates_path = ['_templates']
+
+# The suffix of source filenames.
+source_suffix = '.rst'
+
+# The encoding of source files.
+#source_encoding = 'utf-8'
+
+# The master toctree document.
+master_doc = 'index'
+
+# General information about the project.
+project = u'Tuikit'
+copyright = u'2011, Radek Brich'
+
+# The version info for the project you're documenting, acts as replacement for
+# |version| and |release|, also used in various other places throughout the
+# built documents.
+#
+# The short X.Y version.
+version = '0.5'
+# The full version, including alpha/beta/rc tags.
+release = '0.5a'
+
+# The language for content autogenerated by Sphinx. Refer to documentation
+# for a list of supported languages.
+#language = None
+
+# There are two options for replacing |today|: either, you set today to some
+# non-false value, then it is used:
+#today = ''
+# Else, today_fmt is used as the format for a strftime call.
+#today_fmt = '%B %d, %Y'
+
+# List of documents that shouldn't be included in the build.
+#unused_docs = []
+
+# List of directories, relative to source directory, that shouldn't be searched
+# for source files.
+exclude_trees = ['_build']
+
+# The reST default role (used for this markup: `text`) to use for all documents.
+#default_role = None
+
+# If true, '()' will be appended to :func: etc. cross-reference text.
+#add_function_parentheses = True
+
+# If true, the current module name will be prepended to all description
+# unit titles (such as .. function::).
+#add_module_names = True
+
+# If true, sectionauthor and moduleauthor directives will be shown in the
+# output. They are ignored by default.
+#show_authors = False
+
+# The name of the Pygments (syntax highlighting) style to use.
+pygments_style = 'sphinx'
+
+# A list of ignored prefixes for module index sorting.
+#modindex_common_prefix = []
+
+
+# -- Options for HTML output ---------------------------------------------------
+
+# The theme to use for HTML and HTML Help pages. Major themes that come with
+# Sphinx are currently 'default' and 'sphinxdoc'.
+html_theme = 'default'
+
+# Theme options are theme-specific and customize the look and feel of a theme
+# further. For a list of options available for each theme, see the
+# documentation.
+#html_theme_options = {}
+
+# Add any paths that contain custom themes here, relative to this directory.
+#html_theme_path = []
+
+# The name for this set of Sphinx documents. If None, it defaults to
+# "<project> v<release> documentation".
+#html_title = None
+
+# A shorter title for the navigation bar. Default is the same as html_title.
+#html_short_title = None
+
+# The name of an image file (relative to this directory) to place at the top
+# of the sidebar.
+#html_logo = None
+
+# The name of an image file (within the static path) to use as favicon of the
+# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
+# pixels large.
+#html_favicon = None
+
+# Add any paths that contain custom static files (such as style sheets) here,
+# relative to this directory. They are copied after the builtin static files,
+# so a file named "default.css" will overwrite the builtin "default.css".
+html_static_path = ['_static']
+
+# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
+# using the given strftime format.
+#html_last_updated_fmt = '%b %d, %Y'
+
+# If true, SmartyPants will be used to convert quotes and dashes to
+# typographically correct entities.
+#html_use_smartypants = True
+
+# Custom sidebar templates, maps document names to template names.
+#html_sidebars = {}
+
+# Additional templates that should be rendered to pages, maps page names to
+# template names.
+#html_additional_pages = {}
+
+# If false, no module index is generated.
+#html_use_modindex = True
+
+# If false, no index is generated.
+#html_use_index = True
+
+# If true, the index is split into individual pages for each letter.
+#html_split_index = False
+
+# If true, links to the reST sources are added to the pages.
+#html_show_sourcelink = True
+
+# If true, an OpenSearch description file will be output, and all pages will
+# contain a <link> tag referring to it. The value of this option must be the
+# base URL from which the finished HTML is served.
+#html_use_opensearch = ''
+
+# If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml").
+#html_file_suffix = ''
+
+# Output file base name for HTML help builder.
+htmlhelp_basename = 'Tuikitdoc'
+
+
+# -- Options for LaTeX output --------------------------------------------------
+
+# The paper size ('letter' or 'a4').
+#latex_paper_size = 'letter'
+
+# The font size ('10pt', '11pt' or '12pt').
+#latex_font_size = '10pt'
+
+# Grouping the document tree into LaTeX files. List of tuples
+# (source start file, target name, title, author, documentclass [howto/manual]).
+latex_documents = [
+ ('index', 'Tuikit.tex', u'Tuikit Documentation',
+ u'Radek Brich', 'manual'),
+]
+
+# The name of an image file (relative to this directory) to place at the top of
+# the title page.
+#latex_logo = None
+
+# For "manual" documents, if this is true, then toplevel headings are parts,
+# not chapters.
+#latex_use_parts = False
+
+# Additional stuff for the LaTeX preamble.
+#latex_preamble = ''
+
+# Documents to append as an appendix to all manuals.
+#latex_appendices = []
+
+# If false, no module index is generated.
+#latex_use_modindex = True
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/focus.rst Wed Feb 16 23:51:30 2011 +0100
@@ -0,0 +1,14 @@
+Focus
+=====
+
+Only one non-container widget can have focus at the time.
+All parent containers also have focus.
+
+Events emitted on change: focus, unfocus
+
+mousedown - focus widget under mouse or its parent if canfocus() == false
+
+tab - focus next child in container
+shift-tab - previous child
+
+hide() -> unfocus
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/index.rst Wed Feb 16 23:51:30 2011 +0100
@@ -0,0 +1,22 @@
+.. Tuikit documentation master file, created by
+ sphinx-quickstart on Wed Feb 2 23:35:28 2011.
+ You can adapt this file completely to your liking, but it should at least
+ contain the root `toctree` directive.
+
+Welcome to Tuikit's documentation!
+==================================
+
+Contents:
+
+.. toctree::
+ :maxdepth: 2
+
+ redraw
+
+Indices and tables
+==================
+
+* :ref:`genindex`
+* :ref:`modindex`
+* :ref:`search`
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/example.py Wed Feb 16 23:51:30 2011 +0100
@@ -0,0 +1,76 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+import locale
+locale.setlocale(locale.LC_ALL, '')
+
+import os
+
+from tuikit.application import Application
+from tuikit.editfield import EditField
+from tuikit.window import Window
+from tuikit.button import Button
+from tuikit.scrollbar import VScrollbar
+from tuikit.textedit import TextEdit
+from tuikit.menubar import MenuBar
+from tuikit.menu import Menu
+from tuikit.layout import VerticalLayout
+
+
+class MyApplication(Application):
+ def __init__(self):
+ Application.__init__(self)
+ self.top.connect('keypress', self.globalkeypress)
+
+ menubar = MenuBar()
+ self.top.add(menubar)
+
+ filemenu = Menu(['New', '-', 'Open', 'Save', '-', 'Quit'])
+ self.top.add(filemenu)
+ filemenu.allowlayout = False
+ filemenu.hidden = True
+
+ editmenu = Menu(['Copy', 'Paste'])
+ self.top.add(editmenu)
+ editmenu.allowlayout = False
+ editmenu.hidden = True
+
+ helpmenu = Menu(['About'])
+ self.top.add(helpmenu)
+ helpmenu.allowlayout = False
+ helpmenu.hidden = True
+
+ menubar.setitems([
+ ('File', filemenu),
+ ('Edit', editmenu),
+ ('Help', helpmenu)
+ ])
+
+ vert = VerticalLayout()
+ self.top.layout(vert)
+
+
+ #win = Window()
+ #self.top.add(win)
+
+ #button = Button('click!')
+ #win.add(button)
+ #button.x = 10
+ #button.y = 7
+
+ #button.connect('click', self.buttonclick)
+ #self.button = button
+
+ #subwin = Window(8,8)
+ #win.add(subwin)
+
+
+ def globalkeypress(self, keyname, char):
+ if keyname == 'escape':
+ self.terminate()
+
+
+if __name__ == '__main__':
+ app = MyApplication()
+ app.start()
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tuikit/application.py Wed Feb 16 23:51:30 2011 +0100
@@ -0,0 +1,110 @@
+# -*- coding: utf-8 -*-
+
+import curses
+import curses.wrapper
+import logging
+import time
+import math
+
+from .container import Container
+from .backend_curses import BackendCurses
+
+
+class TopWindow(Container):
+ def __init__(self):
+ Container.__init__(self)
+
+ self.top = self
+
+ self.timeout = []
+ self.timelast = None
+
+ self.connect('draw', self.on_draw)
+
+
+ def on_draw(self, screen, x, y):
+ screen.erase()
+
+
+ def add_timeout(self, s, func):
+ if not len(self.timeout):
+ self.timelast = time.time()
+ self.timeout += [[s, func]]
+
+
+ def remove_timeout(self, func):
+ for to in self.timeout[:]:
+ if to[1] == func:
+ self.timeout.remove(to)
+
+
+ def has_timeout(self):
+ return len(self.timeout) and True or False
+
+
+ def nearest_timeout(self):
+ return min(self.timeout)[0]
+
+
+ def process_timeout(self):
+ now = time.time()
+ lasted = now - self.timelast
+ self.timelast = now
+
+ for to in self.timeout[:]:
+ newt = to[0] - lasted
+ if newt <= 0.0:
+ to[1]()
+ self.timeout.remove(to)
+ else:
+ to[0] = newt
+
+
+class Application:
+ def __init__(self):
+ self.top = TopWindow()
+ self.quit = False
+ self.screen = None
+
+ self.log = logging.getLogger('tuikit')
+ self.log.setLevel(logging.DEBUG)
+ handler = logging.FileHandler('./tuikit.log')
+ format = logging.Formatter('%(asctime)s %(levelname)-5s %(message)s', '%y-%m-%d %H:%M:%S')
+ handler.setFormatter(format)
+ self.log.addHandler(handler)
+ self.log.info('start')
+
+
+ def start(self):
+ curses.wrapper(self.mainloop)
+
+
+ def terminate(self):
+ self.quit = True
+
+
+ def mainloop(self, screen):
+ self.screen = BackendCurses(screen)
+ self.top.width, self.top.height = self.screen.width, self.screen.height
+ self.top.emit('resize')
+ self.top.setfocus()
+
+ while True:
+ self.top.draw(self.screen)
+ self.screen.commit()
+
+ timeout = None
+ if self.top.has_timeout():
+ timeout = int(math.ceil(self.top.nearest_timeout() * 10))
+
+ events = self.screen.process_input(timeout)
+
+ if self.top.has_timeout():
+ self.top.process_timeout()
+
+ for event in events:
+ self.top.emit(event[0], *event[1:])
+
+ if self.quit:
+ break
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tuikit/backend_curses.py Wed Feb 16 23:51:30 2011 +0100
@@ -0,0 +1,408 @@
+# -*- coding: utf-8 -*-
+
+import curses
+import curses.ascii
+import locale
+import logging
+
+from .common import Rect
+
+
+class MouseEvent:
+ def __init__(self, x=0, y=0):
+ self.x = x # global coordinates
+ self.y = y
+ self.wx = x # local widget coordinates
+ self.wy = y
+ self.px = 0 # parent coordinates
+ self.py = 0
+ self.button = 0
+
+
+ def childevent(self, child):
+ ev = MouseEvent(self.x, self.y)
+ # original local coordinates are new parent coordinates
+ ev.px = self.wx
+ ev.py = self.wy
+ # update local coordinates
+ ev.wx = self.wx - child.x
+ ev.wy = self.wy - child.y
+
+ return ev
+
+
+class BackendCurses:
+ xterm_codes = (
+ (0x09, 'tab' ),
+ (0x0a, 'enter' ),
+ (0x7f, 'backspace' ),
+ (0x1b,0x4f,0x50, 'f1' ),
+ (0x1b,0x4f,0x51, 'f2' ),
+ (0x1b,0x4f,0x52, 'f3' ),
+ (0x1b,0x4f,0x53, 'f4' ),
+ (0x1b,0x5b,0x4d, 'mouse' ),
+ (0x1b,0x5b,0x41, 'up' ),
+ (0x1b,0x5b,0x42, 'down' ),
+ (0x1b,0x5b,0x43, 'right' ),
+ (0x1b,0x5b,0x44, 'left' ),
+ (0x1b,0x5b,0x31,0x35,0x7e, 'f5' ),
+ (0x1b,0x5b,0x31,0x37,0x7e, 'f6' ),
+ (0x1b,0x5b,0x31,0x38,0x7e, 'f7' ),
+ (0x1b,0x5b,0x31,0x39,0x7e, 'f8' ),
+ (0x1b,0x5b,0x32,0x30,0x7e, 'f9' ),
+ (0x1b,0x5b,0x32,0x31,0x7e, 'f10' ),
+ (0x1b,0x5b,0x32,0x33,0x7e, 'f11' ),
+ (0x1b,0x5b,0x32,0x34,0x7e, 'f12' ),
+ (0x1b,0x5b,0x32,0x7e, 'insert' ),
+ (0x1b,0x5b,0x33,0x7e, 'delete' ),
+ (0x1b,0x5b,0x35,0x7e, 'pageup' ),
+ (0x1b,0x5b,0x36,0x7e, 'pagedown' ),
+ (0x1b,0x5b,0x46, 'end' ),
+ (0x1b,0x5b,0x48, 'home' ),
+ (0x1b, 'escape' ),
+ )
+
+ def __init__(self, screen):
+ self.screen = screen
+ self.height, self.width = screen.getmaxyx()
+
+ self.clipstack = []
+ self.colorstack = []
+ self.inputqueue = []
+ self.mbtnstack = []
+
+ self.log = logging.getLogger('tuikit')
+
+ # initialize curses
+ curses.curs_set(False)
+ curses.mousemask(curses.ALL_MOUSE_EVENTS | curses.REPORT_MOUSE_POSITION)
+ curses.mouseinterval(0) # do not wait to detect clicks, we use only press/release
+
+ screen.immedok(0)
+ screen.keypad(0)
+
+ curses.init_pair(1, curses.COLOR_WHITE, curses.COLOR_BLACK)
+ curses.init_pair(2, curses.COLOR_BLACK, curses.COLOR_CYAN)
+ curses.init_pair(3, curses.COLOR_WHITE, curses.COLOR_CYAN)
+
+ self.BOLD = curses.A_BOLD
+ self.BLINK = curses.A_BLINK
+ self.UNDERLINE = curses.A_UNDERLINE
+
+ # http://en.wikipedia.org/wiki/List_of_Unicode_characters#Geometric_shapes
+ self.UP_ARROW = '▲' #curses.ACS_UARROW
+ self.DOWN_ARROW = '▼' #curses.ACS_DARROW
+
+ # http://en.wikipedia.org/wiki/Box-drawing_characters
+ self.LIGHT_SHADE = '░' #curses.ACS_BOARD
+ self.MEDIUM_SHADE = '▒'
+ self.DARK_SHADE = '▓'
+ self.BLOCK = '█'
+
+ self.COLUMN = '▁▂▃▄▅▆▇█'
+ self.CORNER_ROUND = '╭╮╰╯'
+ self.CORNER = '┌┐└┘'
+ self.LINE = '─━│┃┄┅┆┇┈┉┊┋'
+
+ self.HLINE = '─' # curses.ACS_HLINE
+ self.VLINE = '│' # curses.ACS_VLINE
+ self.ULCORNER = '┌' # curses.ACS_ULCORNER
+ self.URCORNER = '┐' # curses.ACS_URCORNER
+ self.LLCORNER = '└' # curses.ACS_LLCORNER
+ self.LRCORNER = '┘' # curses.ACS_LRCORNER
+ self.LTEE = '├'
+ self.RTEE = '┤'
+
+
+ ## clip operations ##
+
+ def pushclip(self, x, y, w, h):
+ newclip = Rect(x, y, w, h)
+ if len(self.clipstack):
+ oldclip = self.clipstack[-1]
+ newclip = self.intersect(oldclip, newclip)
+ self.clipstack.append(newclip)
+
+
+ def popclip(self):
+ self.clipstack.pop()
+
+
+ def testclip(self, x, y):
+ # no clip rectangle on stack => passed
+ if not len(self.clipstack):
+ return True
+ # test against top clip rect from stack
+ clip = self.clipstack[-1]
+ if x < clip.x or y < clip.y \
+ or x >= clip.x + clip.w or y >= clip.y + clip.h:
+ return False
+ # passed
+ return True
+
+
+ def intersect(self, r1, r2):
+ x1 = max(r1.x, r2.x)
+ y1 = max(r1.y, r2.y)
+ x2 = min(r1.x + r1.w, r2.x + r2.w)
+ y2 = min(r1.y + r1.h, r2.y + r2.h)
+ if x1 >= x2 or y1 >= y2:
+ return Rect()
+ return Rect(x1, y1, x2-x1, y2-y1)
+
+
+ def union(self, r1, r2):
+ x = min(r1.x, r2.x)
+ y = min(r1.y, r2.y)
+ w = max(r1.x + r1.w, r2.x + r2.w) - x
+ h = max(r1.y + r1.h, r2.y + r2.h) - y
+ return Rect(x, y, w, h)
+
+
+ ## attributes ##
+
+ def pushcolor(self, col, attr=0):
+ self.screen.attrset(curses.color_pair(col) | attr)
+ self.colorstack.append((col, attr))
+
+
+ def popcolor(self):
+ self.colorstack.pop()
+ if len(self.colorstack):
+ col, attr = self.colorstack[-1]
+ else:
+ col, attr = 0, 0
+ self.screen.attrset(curses.color_pair(col) | attr)
+
+
+ ## drawing ##
+
+ def putch(self, x, y, c):
+ if not self.testclip(x, y):
+ return
+ try:
+ if type(c) is str and len(c) == 1:
+ self.screen.addstr(y, x, c)
+ else:
+ self.screen.addch(y, x, c)
+ except curses.error:
+ pass
+
+
+ def puts(self, x, y, s):
+ for c in s:
+ self.putch(x, y, c)
+ x += 1
+
+
+ def hline(self, x, y, w, c=' '):
+ if type(c) is str:
+ s = c*w
+ else:
+ s = [c]*w
+ self.puts(x, y, s)
+
+
+ def vline(self, x, y, h, c=' '):
+ for i in range(h):
+ self.putch(x, y+i, c)
+
+
+ def frame(self, x, y, w, h):
+ self.putch(x, y, self.ULCORNER)
+ self.putch(x+w-1, y, self.URCORNER)
+ self.putch(x, y+h-1, self.LLCORNER)
+ self.putch(x+w-1, y+h-1, self.LRCORNER)
+ self.hline(x+1, y, w-2, self.HLINE)
+ self.hline(x+1, y+h-1, w-2, self.HLINE)
+ self.vline(x, y+1, h-2, self.VLINE)
+ self.vline(x+w-1, y+1, h-2, self.VLINE)
+
+
+ def fill(self, x, y, w, h, c=' '):
+ for i in range(h):
+ self.hline(x, y + i, w, c)
+
+
+ def erase(self):
+ curses.curs_set(False)
+ self.cursor = None
+ self.screen.erase()
+
+
+ def commit(self):
+ if self.cursor:
+ self.screen.move(*self.cursor)
+ curses.curs_set(True)
+ else:
+ curses.curs_set(False)
+ self.screen.refresh()
+
+
+ ## cursor ##
+
+ def showcursor(self, x, y):
+ if not self.testclip(x, y):
+ return
+ self.cursor = (y, x)
+
+
+ ## input ##
+
+ def inputqueue_fill(self):
+ self.screen.nodelay(1)
+
+ while True:
+ c = self.screen.getch()
+ if c == -1:
+ break
+ self.inputqueue.insert(0, c)
+
+ self.screen.nodelay(0)
+
+
+ def inputqueue_next(self):
+ c = None
+ while c is None:
+ try:
+ c = self.inputqueue.pop()
+ except IndexError:
+ curses.napms(25)
+ self.inputqueue_fill()
+ return c
+
+
+ def process_input(self, timeout=None):
+ if len(self.inputqueue) > 0:
+ c = self.inputqueue_next()
+ else:
+ if not timeout is None:
+ curses.halfdelay(timeout)
+ c = self.screen.getch()
+ curses.cbreak()
+ if c == -1:
+ return []
+ else:
+ c = self.screen.getch()
+
+ if c == curses.KEY_MOUSE:
+ return self.process_mouse()
+
+ elif curses.ascii.isctrl(c):
+ self.inputqueue.append(c)
+ self.inputqueue_fill()
+ return self.process_control_chars()
+
+ elif c >= 192 and c <= 255:
+ self.inputqueue.append(c)
+ self.inputqueue_fill()
+ return self.process_utf8_chars()
+
+ elif curses.ascii.isprint(c):
+ return [('keypress', None, str(chr(c)))]
+
+ else:
+ #self.top.keypress(None, unicode(chr(c)))
+ self.inputqueue.append(c)
+ self.inputqueue_fill()
+ return self.process_control_chars()
+
+
+ def process_mouse(self):
+ id, x, y, z, bstate = curses.getmouse()
+ ev = MouseEvent(x, y)
+
+ out = []
+
+ if bstate & curses.REPORT_MOUSE_POSITION:
+ out += [('mousemove', ev)]
+
+ if bstate & curses.BUTTON1_PRESSED:
+ ev.button = 1
+ out += [('mousedown', ev)]
+
+ if bstate & curses.BUTTON3_PRESSED:
+ ev.button = 3
+ out += [('mousedown', ev)]
+
+ if bstate & curses.BUTTON1_RELEASED:
+ ev.button = 1
+ out += [('mouseup', ev)]
+
+ if bstate & curses.BUTTON3_RELEASED:
+ ev.button = 3
+ out += [('mouseup', ev)]
+
+ return out
+
+
+ def process_utf8_chars(self):
+ #FIXME read exact number of chars as defined by utf-8
+ utf = ''
+ while len(utf) <= 6:
+ c = self.inputqueue_next()
+ utf += chr(c)
+ try:
+ uni = str(utf, 'utf-8')
+ return [('keypress', None, uni)]
+ except UnicodeDecodeError:
+ continue
+ raise Exception('Invalid UTF-8 sequence: %r' % utf)
+
+
+ def process_control_chars(self):
+ keyname = None
+ for code in self.xterm_codes:
+ ok = False
+ if len(self.inputqueue) >= len(code) - 1:
+ ok = True
+ for i in range(len(code)-1):
+ if self.inputqueue[-i-1] != code[i]:
+ ok = False
+ break
+
+ if ok:
+ keyname = code[-1]
+ self.inputqueue = self.inputqueue[:-len(code)+1]
+
+ if keyname is None:
+ self.log.debug('Unknown control sequence: %s',
+ ','.join(reversed(['0x%x'%x for x in self.inputqueue])))
+ c = self.inputqueue_next()
+ return [('keypress', 'Unknown%x' % c, None)]
+
+ if keyname == 'mouse':
+ return self.process_xterm_mouse()
+
+ return [('keypress', keyname, None)]
+
+
+ def process_xterm_mouse(self):
+ t = self.inputqueue_next()
+ x = self.inputqueue_next() - 0x21
+ y = self.inputqueue_next() - 0x21
+
+ ev = MouseEvent(x, y)
+ out = []
+
+ if t in (0x20, 0x21, 0x22): # button press
+ btn = t - 0x1f
+ ev.button = btn
+ if not btn in self.mbtnstack:
+ self.mbtnstack.append(btn)
+ out += [('mousedown', ev)]
+ else:
+ out += [('mousemove', ev)]
+
+ elif t == 0x23: # button release
+ ev.button = self.mbtnstack.pop()
+ out += [('mouseup', ev)]
+
+ elif t in (0x60, 0x61): # wheel up, down
+ ev.button = 4 + t - 0x60
+ out += [('mousewheel', ev)]
+
+ else:
+ raise Exception('Unknown mouse event: %x' % t)
+
+ return out
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tuikit/button.py Wed Feb 16 23:51:30 2011 +0100
@@ -0,0 +1,47 @@
+# -*- coding: utf-8 -*-
+
+import curses
+import locale
+
+from .widget import Widget
+
+
+class Button(Widget):
+ def __init__(self, label=''):
+ Widget.__init__(self, len(label) + 4, 1)
+
+ self.label = label
+ self.bg = 1
+
+ self.connect('draw', self.on_draw)
+ self.connect('mousedown', self.on_mousedown)
+ self.connect('mouseup', self.on_mouseup)
+
+ self.newevent('click')
+
+
+ def on_draw(self, screen, x, y):
+ l = (self.width - len(self.label)) // 2
+ screen.pushcolor(self.bg)
+ screen.putch(x, y, '<')
+ for i in range(x+1, x+l):
+ screen.putch(i, y, ' ')
+ screen.puts(x + l, y, self.label)
+ for i in range(x+l+len(self.label), x+self.width-1):
+ screen.putch(i, y, ' ')
+ screen.putch(x + self.width - 1, y, '>')
+ screen.popcolor()
+
+
+ def on_mousedown(self, ev):
+ self.bg = 2
+ self.redraw()
+
+
+ def on_mouseup(self, ev):
+ self.bg = 1
+ self.redraw()
+
+ if self.enclose(ev.px, ev.py):
+ self.handle('click')
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tuikit/common.py Wed Feb 16 23:51:30 2011 +0100
@@ -0,0 +1,13 @@
+# -*- coding: utf-8 -*-
+
+class Rect:
+ def __init__(self, x=0, y=0, w=0, h=0):
+ self.x = x
+ self.y = y
+ self.w = w
+ self.h = h
+
+
+ def __repr__(self):
+ return 'Rect(%(x)s,%(y)s,%(w)s,%(h)s)' % self.__dict__
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tuikit/container.py Wed Feb 16 23:51:30 2011 +0100
@@ -0,0 +1,139 @@
+# -*- coding: utf-8 -*-
+
+from .widget import Widget
+
+
+class Container(Widget):
+ def __init__(self, width = 10, height = 10):
+ Widget.__init__(self, width, height)
+
+ self.children = []
+ self.focuschild = None
+ self.mousechild = None
+
+ self.borders = (0, 0, 0, 0) # left, top, right, bottom
+ self.widthrequest = (None, None)
+ self.heightrequest = (None, None)
+
+
+ def add(self, widget):
+ self.children.append(widget)
+ widget.parent = self
+ widget.settop(self.top)
+
+
+ def layout(self, layout):
+ self.layout = layout
+ layout.container = self
+ self.connect('resize', layout.resize)
+
+
+ def settop(self, top):
+ self.top = top
+ for child in self.children:
+ child.settop(top)
+
+
+ ### focus
+
+
+ def canfocus(self):
+ return True
+
+
+ def setfocus(self):
+ self.focus = True
+ if self.focuschild is None and len(self.children) > 0:
+ for child in self.children:
+ if self.focuschild is None:
+ if child.canfocus():
+ self.focuschild = child
+ child.setfocus()
+ else:
+ if child.focus:
+ child.unfocus()
+
+ def unfocus(self):
+ self.focus = False
+ for child in self.children:
+ child.unfocus()
+
+
+ ###
+
+
+ def draw(self, screen, x=0, y=0):
+ #if self._redraw:
+ #self.fill(screen, self.y, self.x, self.height, self.width)
+ self.handle('draw', screen, x, y)
+
+ l, t, r, b = self.borders
+ screen.pushclip(x + l, y + t, self.width - l - r, self.height - t - b)
+
+ for child in self.children:
+ #if self._redraw:
+ # child._redraw = True
+ child.draw(screen, x + child.x, y + child.y)
+
+ screen.popclip()
+
+ #self._redraw = False
+
+
+ def keypress(self, keyname, char):
+ # always relay key event to some child
+ if self.focus:
+ self.handle('keypress', keyname, char)
+ if self.focuschild:
+ self.focuschild.keypress(keyname, char)
+
+
+ def mousedown(self, ev):
+ handled = False
+ for child in reversed(self.children):
+ if child.enclose(ev.wx, ev.wy):
+ childev = ev.childevent(child)
+ child.mousedown(childev)
+ self.mousechild = child
+ handled = True
+ break
+ if not handled:
+ self.handle('mousedown', ev)
+
+
+ def mouseup(self, ev):
+ if self.mousechild:
+ childev = ev.childevent(self.mousechild)
+ self.mousechild.mouseup(childev)
+ self.mousechild = None
+ else:
+ self.handle('mouseup', ev)
+ #handled = False
+ #for child in self.children:
+ #if child.enclose(ev.wx, ev.wy):
+ #childev = ev.childevent(child)
+ #child.mouseup(childev)
+ #self.mousechild = child
+ #handled = True
+ #if not handled:
+ #self.handle('mouseup', ev)
+
+
+ def mousemove(self, ev):
+ if self.mousechild:
+ childev = ev.childevent(self.mousechild)
+ self.mousechild.mousemove(childev)
+ else:
+ self.handle('mousemove', ev)
+
+
+ def mousewheel(self, ev):
+ handled = False
+ for child in reversed(self.children):
+ if child.enclose(ev.wx, ev.wy):
+ childev = ev.childevent(child)
+ child.mousewheel(childev)
+ handled = True
+ break
+ if not handled:
+ self.handle('mousewheel', ev)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tuikit/editbox.py Wed Feb 16 23:51:30 2011 +0100
@@ -0,0 +1,210 @@
+# -*- coding: utf-8 -*-
+
+from .widget import Widget
+
+class EditBox(Widget):
+ def __init__(self, width=20, height=20, text=''):
+ Widget.__init__(self, width, height)
+
+ self.set_text(text)
+
+ self.xofs = 0
+ self.yofs = 0
+
+ # cursor
+ self.cline = 0
+ self.cpos = 0
+
+ # selection
+ self.sline = 0
+ self.spos = 0
+
+ self.connect('draw', self.on_draw)
+ self.connect('keypress', self.on_keypress)
+ self.connect('mousewheel', self.on_mousewheel)
+
+ self.newevent('scroll')
+ self.newevent('areasize')
+
+
+ def on_draw(self, screen, x, y):
+ for j in range(self.height):
+ if self.yofs + j >= len(self.lines):
+ break
+ line = self.lines[self.yofs + j]
+ #if len(line) < self.width:
+ #line += ' ' * (self.width - len(line))
+ #else:
+ #line = line[:self.width]
+ screen.puts(x, y + j, line)
+
+ screen.showcursor(x + self.get_cpos() - self.xofs, y + self.cline - self.yofs)
+
+
+ def on_keypress(self, keyname, char):
+ if keyname:
+ if keyname == 'left':
+ self.move_left()
+
+ if keyname == 'right':
+ self.move_right()
+
+ if keyname == 'home':
+ self.move_home()
+
+ if keyname == 'end':
+ self.move_end()
+
+ if keyname == 'up':
+ self.move_up()
+
+ if keyname == 'down':
+ self.move_down()
+
+ if keyname == 'pageup':
+ self.move_pageup()
+
+ if keyname == 'pagedown':
+ self.move_pagedown()
+
+ if keyname == 'backspace':
+ if self.cline > 0 or self.cpos > 0:
+ self.move_left()
+ self.del_char()
+
+ if keyname == 'delete':
+ self.del_char()
+
+ if keyname == 'enter':
+ self.add_newline()
+ self.move_right()
+
+ if char:
+ self.add_char(char)
+ self.move_right()
+
+ self.redraw()
+
+
+ def on_mousewheel(self, ev):
+ # up
+ if ev.button == 4:
+ self.move_up()
+ # down
+ if ev.button == 5:
+ self.move_down()
+ self.redraw()
+
+
+ def set_text(self, text):
+ self.lines = text.split('\n')
+
+
+ def get_text(self):
+ return '\n'.join(self.lines)
+
+
+ def get_linelen(self):
+ return len(self.lines[self.cline])
+
+
+ def get_cpos(self):
+ if self.cpos > self.get_linelen():
+ return self.get_linelen()
+ return self.cpos
+
+
+ def set_yofs(self, yofs):
+ if yofs < 0:
+ yofs = 0
+ if yofs > len(self.lines) - self.height:
+ yofs = len(self.lines) - self.height
+ self.yofs = yofs
+ self.handle('scroll')
+
+
+ def move_left(self):
+ if self.cpos > 0:
+ self.cpos = self.get_cpos() - 1
+ else:
+ if self.move_up():
+ self.cpos = self.get_linelen()
+
+
+ def move_right(self):
+ if self.cpos < self.get_linelen():
+ self.cpos += 1
+ else:
+ if self.move_down():
+ self.cpos = 0
+
+
+ def move_home(self):
+ self.cpos = 0
+
+
+ def move_end(self):
+ self.cpos = self.get_linelen()
+
+
+ def move_up(self):
+ if self.cline > 0:
+ self.cline -= 1
+ if self.cline < self.yofs:
+ self.set_yofs(self.cline)
+ return True
+ return False
+
+
+ def move_down(self):
+ if self.cline < len(self.lines) - 1:
+ self.cline += 1
+ if self.cline > self.yofs + self.height - 1:
+ self.set_yofs(self.cline - (self.height - 1))
+ return True
+ return False
+
+
+ def move_pageup(self):
+ if self.cline >= self.height - 1:
+ self.cline -= self.height - 1
+ self.set_yofs(self.yofs - (self.height - 1))
+ else:
+ self.cline = 0
+ self.set_yofs(0)
+
+
+ def move_pagedown(self):
+ if self.cline <= len(self.lines) - (self.height - 1):
+ self.cline += self.height - 1
+ self.set_yofs(self.yofs + (self.height - 1))
+ else:
+ self.cline = len(self.lines) - 1
+ self.set_yofs(self.cline)
+
+
+ def add_char(self, c):
+ ln = self.lines[self.cline]
+ cpos = self.get_cpos()
+ self.lines[self.cline] = ln[:cpos] + c + ln[cpos:]
+ self.cpos = cpos
+
+
+ def add_newline(self):
+ ln = self.lines[self.cline]
+ cpos = self.get_cpos()
+ self.lines[self.cline] = ln[cpos:]
+ self.lines.insert(self.cline, ln[:cpos])
+ self.handle('areasize')
+
+
+ def del_char(self):
+ ln = self.lines[self.cline]
+ cpos = self.get_cpos()
+ if cpos == self.get_linelen():
+ if self.cline + 1 < len(self.lines):
+ self.lines[self.cline] = self.lines[self.cline] + self.lines[self.cline+1]
+ del self.lines[self.cline+1]
+ self.handle('areasize')
+ else:
+ self.lines[self.cline] = ln[:cpos] + ln[cpos+1:]
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tuikit/editfield.py Wed Feb 16 23:51:30 2011 +0100
@@ -0,0 +1,104 @@
+# -*- coding: utf-8 -*-
+
+import curses
+import locale
+
+from .widget import Widget
+
+
+class EditField(Widget):
+ def __init__(self, width=10, value=''):
+ Widget.__init__(self, width, 1)
+
+ self.code = locale.getpreferredencoding()
+ if not type(value) is str:
+ value = str(value, self.code)
+
+ self.value = value
+ self.maxlen = None # unlimited
+
+ self.tw = self.width - 2 # real width of text field (minus space for arrows)
+ self.cursor = 0 # position of cursor on screen
+ self.pos = 0 # position of cursor in value
+ self.ofs = 0 # position of value beginning on screen
+
+ self.connect('draw', self.on_draw)
+ self.connect('keypress', self.on_keypress)
+
+
+ def on_draw(self, screen, x, y):
+ # draw value
+ val = self.value + ' ' * self.tw # add spaces to fill rest of field
+ val = val[self.ofs : self.ofs + self.tw] # cut value - begin from ofs, limit to tw chars
+ screen.puts(x + 1, y, val.encode(self.code))
+
+ # draw arrows if content overflows
+ c = ' '
+ if self.ofs > 0:
+ c = '<'
+ screen.putch(x, y, c)
+
+ c = ' '
+ if len(self.value[self.ofs:]) > self.tw:
+ c = '>'
+ screen.putch(x + self.width-1, y, c)
+
+ # move cursor to the position
+ screen.showcursor(x + 1 + self.cursor, y)
+
+
+ def on_keypress(self, keyname, char):
+ if keyname:
+ if keyname == 'left':
+ self.move_left()
+
+ if keyname == 'right':
+ self.move_right()
+
+ if keyname == 'backspace':
+ if self.pos > 0:
+ self.move_left()
+ self.del_char()
+
+ if keyname == 'delete':
+ self.del_char()
+
+ if char:
+ self.add_char(char)
+ self.move_right()
+
+ self.redraw()
+
+
+ def move_left(self):
+ if self.cursor > 1 or (self.cursor == 1 and self.pos == 1):
+ # move cursor
+ self.pos -= 1
+ self.cursor -= 1
+ else:
+ # move content in field
+ if self.pos > self.cursor:
+ self.pos -= 1
+ self.ofs -= 1
+
+
+ def move_right(self):
+ if self.pos < len(self.value):
+ if self.cursor < self.tw - 2 \
+ or (self.cursor == self.tw - 2 and self.pos == len(self.value)-1):
+ # move cursor
+ self.pos += 1
+ self.cursor += 1
+ else:
+ # move content in field
+ self.pos += 1
+ self.ofs += 1
+
+
+ def add_char(self, c):
+ self.value = self.value[:self.pos] + c + self.value[self.pos:]
+
+
+ def del_char(self):
+ self.value = self.value[:self.pos] + self.value[self.pos+1:]
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tuikit/layout.py Wed Feb 16 23:51:30 2011 +0100
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+
+class VerticalLayout:
+ def resize(self):
+ v = 0
+ c = self.container
+ for child in c.children:
+ if not child.allowlayout:
+ continue
+ child.x = 0
+ child.width = c.width
+ child.y = v
+ v += child.height
+ child.handle('resize')
+ c.redraw()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tuikit/menu.py Wed Feb 16 23:51:30 2011 +0100
@@ -0,0 +1,34 @@
+# -*- coding: utf-8 -*-
+
+from .widget import Widget
+
+
+class Menu(Widget):
+ def __init__(self, items=[]):
+ Widget.__init__(self)
+ self.width = max([len(x) for x in items]) + 4
+ self.height = len(items) + 2
+
+ self.bg = 2
+ self.items = items
+
+ self.connect('draw', self.on_draw)
+ self.connect('keypress', self.on_keypress)
+
+
+ def on_draw(self, screen, x, y):
+ screen.pushcolor(self.bg)
+ screen.frame(x, y, self.width, self.height)
+ i = 1
+ for item in self.items:
+ if item == '-':
+ screen.puts(x, y + i, screen.LTEE + screen.HLINE * (self.width - 2) + screen.RTEE)
+ else:
+ screen.puts(x + 1, y + i, ' ' + item + ' ' * (self.width - 3 - len(item)))
+ i += 1
+ screen.popcolor()
+
+
+ def on_keypress(self, keyname, char):
+ self.redraw()
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tuikit/menubar.py Wed Feb 16 23:51:30 2011 +0100
@@ -0,0 +1,61 @@
+# -*- coding: utf-8 -*-
+
+from .widget import Widget
+
+
+class MenuBar(Widget):
+ def __init__(self, items = []):
+ Widget.__init__(self, 0, 1)
+
+ self.bg = 2
+ self.highlight = 3
+
+ self.setitems(items)
+ self.selected = None
+
+ self.connect('draw', self.on_draw)
+ self.connect('keypress', self.on_keypress)
+ self.connect('mousedown', self.on_mousedown)
+
+
+ def setitems(self, items):
+ self.items = items
+
+
+ def on_draw(self, screen, x, y):
+ screen.pushcolor(self.bg)
+ i = 0
+ for item in self.items:
+ if self.selected == item:
+ screen.pushcolor(self.highlight, screen.BOLD)
+ screen.puts(x + i, y, ' ' + item[0] + ' ')
+ screen.popcolor()
+ else:
+ screen.puts(x + i, y, ' ' + item[0] + ' ')
+ i += len(item[0]) + 4
+ if i < self.width:
+ screen.puts(x + i, y, ' ' * (self.width - i))
+ screen.popcolor()
+
+
+ def on_keypress(self, keyname, char):
+ self.redraw()
+
+
+ def on_mousedown(self, ev):
+ i = 0
+ if self.selected:
+ if isinstance(self.selected[1], Widget):
+ self.selected[1].hidden = True
+ self.selected = None
+ for item in self.items:
+ w = len(item[0]) + 4
+ if ev.wx >= i and ev.wx < i + w:
+ self.selected = item
+ if isinstance(item[1], Widget):
+ item[1].x = i
+ item[1].y = self.y + 1
+ item[1].hidden = False
+ item[1].redraw()
+ i += w
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tuikit/scrollbar.py Wed Feb 16 23:51:30 2011 +0100
@@ -0,0 +1,107 @@
+# -*- coding: utf-8 -*-
+
+from .widget import Widget
+
+
+class VScrollbar(Widget):
+ def __init__(self, height=10):
+ Widget.__init__(self, 1, height)
+
+ self.max = height - 3
+ self.pos = 0
+ self.thumbpos = 0
+
+ self.interval = 0.1
+
+ self.dragging = False
+ self.move = None
+
+ self.connect('draw', self.on_draw)
+ self.connect('mousedown', self.on_mousedown)
+ self.connect('mouseup', self.on_mouseup)
+ self.connect('mousemove', self.on_mousemove)
+
+ self.newevent('change')
+
+
+ def setpos(self, pos):
+ self.pos = pos
+ self.thumbpos = int(round(self.pos / self.max * (self.height - 3)))
+
+
+ def on_draw(self, screen, x, y):
+ screen.putch(x, y, screen.UP_ARROW)
+ for i in range(y + 1, y + self.height - 1):
+ screen.putch(x, i, screen.LIGHT_SHADE)
+ screen.putch(x, y + 1 + self.thumbpos, screen.BLOCK)
+ screen.putch(x, y + self.height - 1, screen.DOWN_ARROW)
+
+
+ def on_mousedown(self, ev):
+ self.dragging = False
+ self.move = None
+ # arrow buttons
+ if ev.wy == 0 or ev.wy == self.height - 1:
+ if ev.wy == 0:
+ self.move_up()
+ else:
+ self.move_down()
+ self.top.add_timeout(self.interval * 2, self.on_timeout)
+ return
+ # thumb bar
+ if ev.wy == 1 + self.thumbpos:
+ self.dragging = True
+ return
+
+
+ def on_mouseup(self, ev):
+ if self.dragging:
+ self.drag(ev.wy)
+ self.dragging = False
+ return
+ if self.move:
+ self.top.remove_timeout(self.on_timeout)
+ self.move = None
+ return
+
+
+ def on_mousemove(self, ev):
+ if self.dragging:
+ self.drag(ev.wy)
+
+
+ def on_timeout(self):
+ if self.move == 'up':
+ self.move_up()
+ if self.move == 'down':
+ self.move_down()
+ self.top.add_timeout(self.interval, self.on_timeout)
+
+
+ def move_up(self):
+ if self.pos > 0:
+ self.setpos(self.pos - 1)
+ self.move = 'up'
+ self.redraw()
+ self.handle('change')
+
+
+ def move_down(self):
+ if self.pos < self.max:
+ self.setpos(self.pos + 1)
+ self.move = 'down'
+ self.redraw()
+ self.handle('change')
+
+
+ def drag(self, wy):
+ newpos = int(round((wy - 1) / (self.height - 3) * self.max))
+ if newpos < 0:
+ newpos = 0
+ if newpos > self.max:
+ newpos = self.max
+ if self.pos != newpos:
+ self.setpos(newpos)
+ self.redraw()
+ self.handle('change')
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tuikit/textedit.py Wed Feb 16 23:51:30 2011 +0100
@@ -0,0 +1,48 @@
+# -*- coding: utf-8 -*-
+
+from .container import Container
+from .editbox import EditBox
+from .scrollbar import VScrollbar
+
+
+class TextEdit(Container):
+ def __init__(self, width=20, height=20, text=''):
+ Container.__init__(self, width, height)
+
+ self.editbox = EditBox(width-2, height-2, text)
+ self.add(self.editbox)
+ self.editbox.x = 1
+ self.editbox.y = 1
+ self.editbox.connect('scroll', self.on_editbox_scroll)
+ self.editbox.connect('areasize', self.on_editbox_areasize)
+
+ self.vscroll = VScrollbar(height - 2)
+ self.add(self.vscroll)
+ self.vscroll.x = width - 1
+ self.vscroll.y = 1
+ self.vscroll.connect('change', self.on_vscroll_change)
+
+ self.on_editbox_areasize()
+
+ self.connect('draw', self.on_draw)
+
+
+ def settext(self, text):
+ self.editbox.set_text(text)
+
+
+ def on_draw(self, screen, x, y):
+ screen.frame(x, y, self.width, self.height)
+
+
+ def on_editbox_scroll(self):
+ self.vscroll.setpos(self.editbox.yofs)
+
+
+ def on_editbox_areasize(self):
+ self.vscroll.max = len(self.editbox.lines) - self.editbox.height
+
+
+ def on_vscroll_change(self):
+ self.editbox.yofs = self.vscroll.pos
+ self.editbox.redraw()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tuikit/widget.py Wed Feb 16 23:51:30 2011 +0100
@@ -0,0 +1,144 @@
+# -*- coding: utf-8 -*-
+
+import curses
+
+
+class Widget:
+ def __init__(self, width = 10, height = 10):
+ self.parent = None
+ self.top = None
+ # placing - set by parent widget
+ self.x = 0
+ self.y = 0
+ # size
+ self.width = width
+ self.height = height
+ self.allowlayout = True
+ # redraw request
+ self._redraw = True
+ self.focus = False
+ self.hidden = False
+ # event handlers
+ self.event = {
+ 'resize' : [],
+ 'draw' : [],
+ 'keypress' : [],
+ 'mousedown' : [],
+ 'mouseup' : [],
+ 'mousemove' : [],
+ 'mousewheel' : []}
+
+
+ def settop(self, top):
+ self.top = top
+
+
+ def newevent(self, event):
+ self.event[event] = []
+
+
+ def connect(self, event, handler):
+ if event in list(self.event.keys()):
+ self.event[event] += [handler]
+
+
+ def disconnect(self, event, handler=None):
+ if event in list(self.event.keys()):
+ if handler:
+ i = self.event[event].index(handler)
+ del self.event[event][i]
+ else:
+ self.event[event] = []
+
+
+ def handle(self, event, *args, **kwargs):
+ for handler in self.event[event]:
+ handler(*args, **kwargs)
+
+
+ def emit(self, event, *args, **kwargs):
+ getattr(self, event)(*args, **kwargs)
+
+
+ def resize(self):
+ self.handle('resize')
+
+
+ def redraw(self, parent=True):
+ self._redraw = True
+ if parent and self.parent:
+ self.parent._redraw = True
+
+
+ def draw(self, screen, x=0, y=0):
+ #if self._redraw:
+ if not self.hidden:
+ self.handle('draw', screen, x, y)
+ #self._redraw = False
+
+
+ ### focus
+
+
+ def canfocus(self):
+ return bool(self.event['keypress'])
+
+
+ def setfocus(self):
+ self.focus = True
+
+
+ def unfocus(self):
+ self.focus = False
+
+
+ def grabfocus(self):
+ if not self.focus:
+ self.parent.grabfocus(self) # grab focus for me
+
+
+ ###
+
+
+ def keypress(self, keyname, char):
+ if self.focus:
+ self.handle('keypress', keyname, char)
+
+
+ def mousedown(self, ev):
+ self.handle('mousedown', ev)
+
+
+ def mouseup(self, ev):
+ self.handle('mouseup', ev)
+
+
+ def mousemove(self, ev):
+ self.handle('mousemove', ev)
+
+
+ def mousewheel(self, ev):
+ self.handle('mousewheel', ev)
+
+
+ def enclose(self, x, y):
+ if self.hidden:
+ return False
+ if x < self.x or y < self.y \
+ or x >= self.x + self.width or y >= self.y + self.height:
+ return False
+ return True
+
+
+ def screentest(self, y, x):
+ sy, sx = self.screenyx()
+ if y < sy or x < sx or y >= sy + self.height or x >= sx + self.width:
+ return False
+ return True
+
+
+ def screenyx(self):
+ if self.parent:
+ y,x = self.parent.screenyx()
+ return self.y + y, self.x + x
+ return self.y, self.x
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tuikit/window.py Wed Feb 16 23:51:30 2011 +0100
@@ -0,0 +1,65 @@
+# -*- coding: utf-8 -*-
+
+from .container import Container
+
+
+class Window(Container):
+ def __init__(self, width=40, height=10):
+ Container.__init__(self, width, height)
+
+ self.connect('draw', self.on_draw)
+ self.connect('mousedown', self.on_mousedown)
+ self.connect('mouseup', self.on_mouseup)
+ self.connect('mousemove', self.on_mousemove)
+
+ self.title = ' '
+
+
+ def on_draw(self, screen, x, y):
+ screen.frame(x, y, self.width, self.height)
+ screen.fill(x+1, y+1, self.width-2, self.height-2)
+ # screen.addstr(self.title)
+
+
+ def on_mousedown(self, ev):
+ self.dragstart = (ev.wx, ev.wy)
+ if ev.wx >= self.width - 1 and ev.wy >= self.height - 1:
+ self.resizing = True
+ else:
+ self.resizing = False
+ self.origsize = (self.width, self.height)
+ #self.title += '%dP%d ' % (x,y)
+ self.redraw(True)
+
+
+ def on_mouseup(self, ev):
+ #self.title += '%dR%d ' % (x,y)
+
+ if self.resizing:
+ self.width = self.origsize[0] + ev.wx - self.dragstart[0]
+ self.height = self.origsize[1] + ev.wy - self.dragstart[1]
+ else:
+ self.x = ev.px - self.dragstart[0]
+ self.y = ev.py - self.dragstart[1]
+
+ self.redraw(True)
+
+
+ def on_mousemove(self, ev):
+ # self.title += '%dM%d ' % (x,y)
+ if ev.px == self.x + self.dragstart[0] \
+ and ev.py == self.y + self.dragstart[1]:
+ return
+
+ #if x > self.parent.width-self.width:
+ # return
+
+ if self.resizing:
+ self.width = self.origsize[0] + ev.wx - self.dragstart[0]
+ self.height = self.origsize[1] + ev.wy - self.dragstart[1]
+ else:
+ self.x = ev.px - self.dragstart[0]
+ self.y = ev.py - self.dragstart[1]
+
+ self.redraw(True)
+