368 lines
13 KiB
Python
368 lines
13 KiB
Python
#!/usr/bin/python
|
|
# -*- coding: utf-8 -*-
|
|
|
|
"""
|
|
Primitivus: a SAT frontend
|
|
Copyright (C) 2009, 2010 Jérôme Poisson (goffi@goffi.org)
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
"""
|
|
|
|
import urwid
|
|
from urwid.escape import utf8decode
|
|
|
|
class Password(urwid.Edit):
|
|
"""Edit box which doesn't show what is entered (show '*' or other char instead)"""
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
"""Same args than Edit.__init__ with an additional keyword arg 'hidden_char'
|
|
@param hidden_char: char to show instead of what is actually entered: default '*'
|
|
"""
|
|
self.hidden_char=kwargs['hidden_char'] if kwargs.has_key('hidden_char') else '*'
|
|
self.__real_text=''
|
|
super(Password, self).__init__(*args, **kwargs)
|
|
|
|
def set_edit_text(self, text):
|
|
self.__real_text = text
|
|
hidden_txt = len(text)*'*'
|
|
super(Password, self).set_edit_text(hidden_txt)
|
|
|
|
def get_edit_text(self):
|
|
return self.__real_text
|
|
|
|
class AdvancedEdit(urwid.Edit):
|
|
"""Edit box with some custom improvments
|
|
new chars:
|
|
- C-a: like 'home'
|
|
- C-e: like 'end'
|
|
- C-k: remove everything on the right of the cursor
|
|
- C-w: remove the word on the back
|
|
new behaviour: emit a 'click' signal when enter is pressed"""
|
|
signals = urwid.Edit.signals + ['click']
|
|
|
|
def keypress(self, size, key):
|
|
#TODO: insert mode is not managed yet
|
|
if key == 'ctrl a':
|
|
key = 'home'
|
|
elif key == 'ctrl e':
|
|
key = 'end'
|
|
elif key == 'ctrl k':
|
|
self._delete_highlighted()
|
|
self.set_edit_text(self.edit_text[:self.edit_pos])
|
|
elif key == 'ctrl w':
|
|
before = self.edit_text[:self.edit_pos]
|
|
pos = before.rstrip().rfind(" ")+1
|
|
self.set_edit_text(before[:pos] + self.edit_text[self.edit_pos:])
|
|
self.set_edit_pos(pos)
|
|
elif key == 'enter':
|
|
self._emit('click')
|
|
return super(AdvancedEdit, self).keypress(size, key)
|
|
|
|
|
|
class SurroundedText(urwid.FlowWidget):
|
|
"""Text centered on a repeated character (like a Divider, but with a text in the center)"""
|
|
|
|
def __init__(self,text,car=utf8decode('─')):
|
|
self.text=text
|
|
self.car=car
|
|
|
|
def rows(self,size,focus=False):
|
|
return self.display_widget(size, focus).rows(size, focus)
|
|
|
|
def render(self, size, focus=False):
|
|
return self.display_widget(size, focus).render(size, focus)
|
|
|
|
def display_widget(self, size, focus):
|
|
(maxcol,) = size
|
|
middle = (maxcol-len(self.text))/2
|
|
render_text = middle * self.car + self.text + (maxcol - len(self.text) - middle) * self.car
|
|
return urwid.Text(render_text)
|
|
|
|
|
|
|
|
class SelectableText(urwid.FlowWidget):
|
|
"""Text which can be selected with space"""
|
|
signals = ['change']
|
|
|
|
def __init__(self, text, align='left'):
|
|
self.text=unicode(text)
|
|
self.align = align
|
|
self.__selected=False
|
|
|
|
def getValue(self):
|
|
return self.text
|
|
|
|
def setState(self, selected, invisible=False):
|
|
"""Change state
|
|
@param selected: boolean state value
|
|
@param invisible: don't emit change signal if True"""
|
|
assert(type(selected)==bool)
|
|
self.__selected=selected
|
|
self._invalidate()
|
|
if not invisible:
|
|
self._emit("change", self.__selected)
|
|
|
|
def getState(self):
|
|
return self.__selected
|
|
|
|
def selectable(self):
|
|
return True
|
|
|
|
def keypress(self, size, key):
|
|
if key==' ' or key=='enter':
|
|
self.setState(not self.__selected)
|
|
else:
|
|
return key
|
|
|
|
def rows(self,size,focus=False):
|
|
return self.display_widget(size, focus).rows(size, focus)
|
|
|
|
def render(self, size, focus=False):
|
|
return self.display_widget(size, focus).render(size, focus)
|
|
|
|
def display_widget(self, size, focus):
|
|
attr = 'selected' if self.__selected else 'default'
|
|
if focus:
|
|
attr+="_focus"
|
|
return urwid.Text((attr,self.text), align=self.align)
|
|
|
|
class GenericList(urwid.WidgetWrap):
|
|
signals = ['click','change']
|
|
|
|
def __init__(self, options, style=[], align='left', on_click=None, on_change=None, user_data=None):
|
|
"""
|
|
Widget managing list of string and their selection
|
|
@param options: list of strings used for options
|
|
@param style: list of string:
|
|
- 'single' if only one must be selected
|
|
- 'no_first_select' nothing selected when list is first displayed
|
|
- 'can_select_none' if we can select nothing
|
|
@param align: alignement of text inside the list
|
|
@param on_click: method called when click signal is emited
|
|
@param user_data: data sent to the callback for click signal
|
|
"""
|
|
self.single = 'single' in style
|
|
self.no_first_select = 'no_first_select' in style
|
|
self.can_select_none = 'can_select_none' in style
|
|
self.align = align
|
|
self.first_display = True
|
|
|
|
if on_click:
|
|
urwid.connect_signal(self, 'click', on_click, user_data)
|
|
|
|
if on_change:
|
|
urwid.connect_signal(self, 'change', on_change, user_data)
|
|
|
|
self.content = urwid.SimpleListWalker([])
|
|
self.list_box = urwid.ListBox(self.content)
|
|
urwid.WidgetWrap.__init__(self, self.list_box)
|
|
self.changeValues(options)
|
|
|
|
def __onStateChange(self, widget, selected):
|
|
if self.single:
|
|
if not selected and not self.can_select_none:
|
|
#if in single mode, it's forbidden to unselect a value
|
|
widget.setState(True, invisible=True)
|
|
return
|
|
if selected:
|
|
self.unselectAll(invisible=True)
|
|
widget.setState(True, invisible=True)
|
|
self._emit("click")
|
|
|
|
|
|
def unselectAll(self, invisible=False):
|
|
for widget in self.content:
|
|
if widget.getState():
|
|
widget.setState(False, invisible)
|
|
widget._invalidate()
|
|
|
|
def deleteValue(self, value):
|
|
"""Delete the first value equal to the param given"""
|
|
for widget in self.content:
|
|
if widget.getValue() == value:
|
|
self.content.remove(widget)
|
|
self._emit('change')
|
|
return
|
|
raise ValueError("%s ==> %s" % (str(value),str(self.content)))
|
|
|
|
def getSelectedValue(self):
|
|
"""Convenience method to get the value selected as a string in single mode, or None"""
|
|
values = self.getSelectedValues()
|
|
return values[0] if values else None
|
|
|
|
def getAllValues(self):
|
|
"""Return values of all items"""
|
|
return [widget.getValue() for widget in self.content]
|
|
|
|
def getSelectedValues(self):
|
|
"""Return values of selected items"""
|
|
result = []
|
|
for widget in self.content:
|
|
if widget.getState():
|
|
result.append(widget.getValue())
|
|
return result
|
|
|
|
def getDisplayWidget(self):
|
|
return self.list_box
|
|
|
|
def changeValues(self, new_values):
|
|
"""Change all value in one shot"""
|
|
if not self.first_display:
|
|
old_selected = self.getSelectedValues()
|
|
widgets = []
|
|
for option in new_values:
|
|
widget = SelectableText(option, self.align)
|
|
if not self.first_display and option in old_selected:
|
|
widget.setState(True)
|
|
widgets.append(widget)
|
|
urwid.connect_signal(widget, 'change', self.__onStateChange)
|
|
self.content[:] = widgets
|
|
if self.first_display and self.single and new_values and not self.no_first_select:
|
|
self.content[0].setState(True)
|
|
display_widget = self.getDisplayWidget()
|
|
self._set_w(display_widget)
|
|
self._emit('change')
|
|
self.first_display = False
|
|
|
|
def selectValue(self, value):
|
|
self.unselectAll()
|
|
idx = 0
|
|
for widget in self.content:
|
|
if widget.getSelectedValue() == value:
|
|
widget.setState(True)
|
|
self.list_box.set_focus(idx)
|
|
return
|
|
idx+=1
|
|
|
|
class List(urwid.FlowWidget):
|
|
"""FlowWidget list, same arguments as GenericList, with an additional one 'max_height'"""
|
|
signals = ['click','change']
|
|
|
|
def __init__(self, options, style=[], max_height=5, align='left', on_click=None, on_change=None, user_data=None):
|
|
self.genericList = GenericList(options, style, align, on_click, on_change, user_data)
|
|
self.max_height = max_height
|
|
|
|
def selectable(self):
|
|
return True
|
|
|
|
def keypress(self, size, key):
|
|
return self.displayWidget(size,True).keypress(size, key)
|
|
|
|
def unselectAll(self, invisible=False):
|
|
return self.genericList.unselectAll(invisible)
|
|
|
|
def deleteValue(self, value):
|
|
return self.genericList.deleteValue(value)
|
|
|
|
def getSelectedValue(self):
|
|
return self.genericList.getSelectedValue()
|
|
|
|
def getAllValues(self):
|
|
return self.genericList.getAllValues()
|
|
|
|
def getSelectedValues(self):
|
|
return self.genericList.getSelectedValues()
|
|
|
|
def changeValues(self, new_values):
|
|
return self.genericList.changeValues(new_values)
|
|
|
|
def selectValue(self, value):
|
|
return self.genericList.selectValue(value)
|
|
|
|
def render(self, size, focus=False):
|
|
return self.displayWidget(size, focus).render(size, focus)
|
|
|
|
def rows(self, size, focus=False):
|
|
return self.displayWidget(size, focus).rows(size, focus)
|
|
|
|
def displayWidget(self, size, focus):
|
|
list_size = sum([wid.rows(size, focus) for wid in self.genericList.content])
|
|
height = min(list_size,self.max_height) or 1
|
|
return urwid.BoxAdapter(self.genericList, height)
|
|
|
|
## DIALOGS ##
|
|
|
|
class GenericDialog(urwid.WidgetWrap):
|
|
|
|
def __init__(self, widgets_lst, title, style=[], **kwargs):
|
|
frame_header = urwid.AttrMap(urwid.Text(title,'center'),'title')
|
|
|
|
buttons = None
|
|
|
|
if "OK/CANCEL" in style:
|
|
buttons = [urwid.Button(_("Cancel"), kwargs['cancel_cb']),
|
|
urwid.Button(_("Ok"), kwargs['ok_cb'], kwargs['ok_value'])]
|
|
elif "YES/NO" in style:
|
|
buttons = [urwid.Button(_("Yes"), kwargs['yes_cb']),
|
|
urwid.Button(_("No"), kwargs['no_cb'], kwargs['yes_value'])]
|
|
if "OK" in style:
|
|
buttons = [urwid.Button(_("Ok"), kwargs['ok_cb'], kwargs['ok_value'])]
|
|
if buttons:
|
|
buttons_flow = urwid.GridFlow(buttons, max([len(button.get_label()) for button in buttons])+4, 1, 1, 'center')
|
|
widgets_lst.append(buttons_flow)
|
|
body_content = urwid.SimpleListWalker(widgets_lst)
|
|
frame_body = urwid.ListBox(body_content)
|
|
frame = urwid.Frame(frame_body, frame_header)
|
|
decorated_frame = urwid.LineBox(frame)
|
|
urwid.WidgetWrap.__init__(self, decorated_frame)
|
|
|
|
|
|
|
|
class InputDialog(GenericDialog):
|
|
"""Dialog with an edit box"""
|
|
|
|
def __init__(self, title, instrucions, style=['OK/CANCEL'], default_txt = '', **kwargs):
|
|
instr_wid = urwid.Text(instrucions+':')
|
|
edit_box = urwid.Edit(edit_text=default_txt)
|
|
GenericDialog.__init__(self, [instr_wid,edit_box], title, style, ok_value=edit_box, **kwargs)
|
|
|
|
class ConfirmDialog(GenericDialog):
|
|
"""Dialog with buttons for confirm or cancel an action"""
|
|
|
|
def __init__(self, title, style=['YES/NO'], **kwargs):
|
|
GenericDialog.__init__(self, [], title, style, yes_value=None, **kwargs)
|
|
|
|
class Alert(GenericDialog):
|
|
"""Dialog with just a message and a OK button"""
|
|
|
|
def __init__(self, title, message, style=['OK'], **kwargs):
|
|
GenericDialog.__init__(self, [urwid.Text(message, 'center')], title, style, ok_value=None, **kwargs)
|
|
|
|
## CONTAINERS ##
|
|
|
|
class FocusFrame(urwid.Frame):
|
|
"""Frame which manage 'tab' key"""
|
|
|
|
def keypress(self, size, key):
|
|
if key == 'tab':
|
|
focus_list = ('header','body','footer')
|
|
focus_idx = focus_list.index(self.focus_part)
|
|
for i in range(2):
|
|
focus_idx = (focus_idx + 1) % len(focus_list)
|
|
focus_name = focus_list[focus_idx]
|
|
widget = getattr(self,'_'+focus_name)
|
|
if widget!=None and widget.selectable():
|
|
self.set_focus(focus_name)
|
|
|
|
return urwid.Frame.keypress(self, size, key)
|
|
|
|
## DECORATORS ##
|
|
class LabelLine(urwid.LineBox):
|
|
"""Like LineBox, but with a Label centered in the top line"""
|
|
|
|
def __init__(self, original_widget, label_widget):
|
|
urwid.LineBox.__init__(self, original_widget)
|
|
top_columns = self._w.widget_list[0]
|
|
top_columns.widget_list[1] = label_widget
|
|
|