# HG changeset patch # User Radek Brich # Date 1297896690 -3600 # Node ID a35731b5e31a59c1d1192e0fc9b022b2128f43f9 tuikit diff -r 000000000000 -r a35731b5e31a .hgignore --- /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 diff -r 000000000000 -r a35731b5e31a docs/Makefile --- /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 ' where 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." diff -r 000000000000 -r a35731b5e31a docs/conf.py --- /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 +# " v 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 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 diff -r 000000000000 -r a35731b5e31a docs/focus.rst --- /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 diff -r 000000000000 -r a35731b5e31a docs/index.rst --- /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` + diff -r 000000000000 -r a35731b5e31a docs/redraw.rst diff -r 000000000000 -r a35731b5e31a example.py --- /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() + diff -r 000000000000 -r a35731b5e31a tuikit/__init__.py diff -r 000000000000 -r a35731b5e31a tuikit/application.py --- /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 + diff -r 000000000000 -r a35731b5e31a tuikit/backend_curses.py --- /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 diff -r 000000000000 -r a35731b5e31a tuikit/button.py --- /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') + diff -r 000000000000 -r a35731b5e31a tuikit/common.py --- /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__ + diff -r 000000000000 -r a35731b5e31a tuikit/container.py --- /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) diff -r 000000000000 -r a35731b5e31a tuikit/editbox.py --- /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:] diff -r 000000000000 -r a35731b5e31a tuikit/editfield.py --- /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:] + diff -r 000000000000 -r a35731b5e31a tuikit/layout.py --- /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() diff -r 000000000000 -r a35731b5e31a tuikit/menu.py --- /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() + diff -r 000000000000 -r a35731b5e31a tuikit/menubar.py --- /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 + diff -r 000000000000 -r a35731b5e31a tuikit/scrollbar.py --- /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') + diff -r 000000000000 -r a35731b5e31a tuikit/textedit.py --- /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() diff -r 000000000000 -r a35731b5e31a tuikit/widget.py --- /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 diff -r 000000000000 -r a35731b5e31a tuikit/window.py --- /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) +