epour/epour/gui/Widgets.py

348 lines
9.7 KiB
Python

#
# Epour - A bittorrent client using EFL and libtorrent
#
# Copyright 2012-2017 Kai Huuhko <kai.huuhko@gmail.com>
#
# 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, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
# MA 02110-1301, USA.
#
from efl.evas import EVAS_HINT_EXPAND, EVAS_HINT_FILL, Rectangle
from efl.ecore import Timer
from efl.elementary import Box
from efl.elementary import Spinner
from efl.elementary import Hoversel
from efl.elementary import Label
from efl.elementary import Notify
from efl.elementary import Popup
from efl.elementary import Button
from efl.elementary import Grid
from efl.elementary import Fileselector
from efl.elementary import FileselectorButton
EXPAND_BOTH = EVAS_HINT_EXPAND, EVAS_HINT_EXPAND
EXPAND_HORIZ = EVAS_HINT_EXPAND, 0.0
FILL_BOTH = EVAS_HINT_FILL, EVAS_HINT_FILL
FILL_HORIZ = EVAS_HINT_FILL, 0.5
if "xrange" not in dir(__builtins__):
xrange = range
def chunker(seq, size):
return (seq[pos:pos + size] for pos in xrange(0, len(seq), size))
class UnitSpinner(Box):
si_prefixes = ("", "k", "M", "G", "T", "P")
binary_prefixes = ("", "Ki", "Mi", "Gi", "Ti", "Pi")
def __init__(self, parent, unit, base=1000, prefixes=si_prefixes):
self.unit = unit
self.base = base
self.prefixes = prefixes
super(UnitSpinner, self).__init__(parent)
self.horizontal = True
self.save_timer = None
s = self.spinner = Spinner(parent)
s.size_hint_weight = EXPAND_HORIZ
s.size_hint_align = FILL_HORIZ
s.min_max = 0, base
s.show()
self.pack_end(s)
hs = self.hoversel = Hoversel(parent)
hs.hover_parent = parent.top_widget
for p in prefixes:
hs.item_add(p + unit)
hs.show()
self.pack_end(hs)
hs.callback_selected_add(lambda x, y: x.text_set(y.text))
def callback_changed_add(self, func, delay=None):
self.spinner.callback_changed_add(self.changed_cb, func, delay)
self.hoversel.callback_selected_add(self.changed_cb, func, delay)
def changed_cb(self, widget, *args):
func, delay = args[-2:]
if delay:
if self.save_timer is not None:
self.save_timer.delete()
self.save_timer = Timer(2.0, self.save_cb, func)
else:
self.save_cb(func)
def save_cb(self, func):
v = int(self.get_value())
func(self, v)
self.save_timer = None
return False
def get_value(self):
p = self.hoversel.text[:-len(self.unit)]
return self.spinner.value * (
self.base ** self.prefixes.index(p)
)
def set_value(self, v):
i = 0
while v // self.base > 0:
i += 1
v = float(v) / float(self.base)
if i > len(self.prefixes):
i = -1
self.spinner.value = v
self.hoversel.text = self.prefixes[i] + self.unit
class Information(Notify):
def __init__(self, parent, text, button=False, timeout=3, *args, **kwargs):
super(self.__class__, self).__init__(parent)
b = Box(self)
l = Label(b, text=text)
b.pack_end(l)
l.show()
self.content = l
if button:
xbtn = Button(b, text=_("Close"))
xbtn.callback_clicked_add(lambda x: self.delete())
b.pack_end(xbtn)
self.timeout = timeout
self.show()
class Error(Popup):
def __init__(self, parent, title, text):
super(self.__class__, self).__init__(parent)
self.part_text_set("title,text", title)
self.text = text
b = Button(self)
b.text = _("OK")
b.callback_clicked_add(lambda x: self.delete())
self.part_content_set("button1", b)
self.show()
class ConfirmExit(Popup):
def __init__(self, parent, exit_func):
super(self.__class__, self).__init__(parent)
self.part_text_set("title,text", _("Confirm exit"))
self.text = _("Are you sure you wish to exit Epour?")
b = Button(self)
b.text = _("Yes")
b.callback_clicked_add(lambda x: exit_func())
self.part_content_set("button1", b)
b = Button(self)
b.text = _("No")
b.callback_clicked_add(lambda x: self.delete())
self.part_content_set("button2", b)
self.show()
class BlockGraph(Grid):
colors = (
(255, 0, 0, 255),
(255, 255, 0, 255),
(0, 255, 0, 255)
)
def __init__(self, parent, pieces, num_completed_pieces=0, *args, **kwargs):
self.num_completed_pieces = num_completed_pieces
self.pieces = pieces
self.rects = []
super(self.__class__, self).__init__(parent, *args, **kwargs)
self.pack_rects()
@property
def num_total_pieces(self):
return len(self.pieces)
@property
def block_size(self):
width, height = self.size
num_pieces = self.num_total_pieces
grid_size = width * height
block_size = \
num_pieces//grid_size + bool(num_pieces % grid_size)
return block_size
@property
def blocks(self):
blocks = []
if self.block_size == 0:
raise ValueError("Block size 0")
for block in chunker(self.pieces, self.block_size):
if all(block):
blocks.append(2)
elif any(block):
blocks.append(1)
else:
blocks.append(0)
return blocks
def pack_rects(self):
if self.num_total_pieces == 0:
return
blocks = self.blocks
width, height = self.size
len_blocks = len(blocks)
p = 0
for y in xrange(height):
for x in xrange(width):
if p >= len_blocks:
continue
else:
block = blocks[p]
if block == 0:
color = self.colors[0]
elif block == 1:
color = self.colors[1]
else:
color = self.colors[2]
rect = Rectangle(self.evas, color=color)
self.rects.append(rect)
self.pack(rect, x, y, 1, 1)
rect.show()
p += 1
def update(self, pieces, num_pieces):
self.pieces = pieces
if pieces and not self.rects:
self.pack_rects()
elif self.num_completed_pieces == num_pieces:
return
self.num_completed_pieces = num_pieces
width, height = self.size
old_blocks = self.blocks
self._blocks = None
new_blocks = self.blocks
row = 0
col = 0
for ov, nv in zip(old_blocks, new_blocks):
if ov != nv:
self.rects[(row * width) + col].color = self.colors[nv]
if (col + 1) >= width:
row += 1
col = 0
else:
col += 1
return True
class ActSWithLabel(Box):
def __init__(self, parent, label_text, values, initial_value):
Box.__init__(self, parent)
from efl.elementary.actionslider import Actionslider, \
ELM_ACTIONSLIDER_LEFT, ELM_ACTIONSLIDER_CENTER, \
ELM_ACTIONSLIDER_RIGHT, ELM_ACTIONSLIDER_ALL
self.pos_to_v = {
ELM_ACTIONSLIDER_LEFT: values[0],
ELM_ACTIONSLIDER_CENTER: values[1],
ELM_ACTIONSLIDER_RIGHT: values[2],
}
self.v_to_pos = {
values[0]: ELM_ACTIONSLIDER_LEFT,
values[1]: ELM_ACTIONSLIDER_CENTER,
values[2]: ELM_ACTIONSLIDER_RIGHT,
}
self.horizontal = True
self.size_hint_align = FILL_HORIZ
self.size_hint_weight = EXPAND_HORIZ
l = Label(parent)
l.text = label_text
l.show()
w = self.w = Actionslider(parent)
w.magnet_pos = ELM_ACTIONSLIDER_ALL
w.size_hint_align = FILL_HORIZ
w.size_hint_weight = EXPAND_HORIZ
w.show()
parts = "left", "center", "right"
for i, v in enumerate(values):
w.part_text_set(parts[i], str(v))
init_v = self.v_to_pos[initial_value]
w.indicator_pos = init_v
self.pack_end(l)
self.pack_end(w)
def get_value(self):
pos = self.w.indicator_pos
value = self.pos_to_v[pos]
return value
class RangeSpinners(Box):
def __init__(self, parent, low, high, minim, maxim):
Box.__init__(self, parent)
self.size_hint_weight = EXPAND_BOTH
self.size_hint_align = FILL_BOTH
self.horizontal = True
l = self.listenlow = Spinner(parent)
l.size_hint_weight = EXPAND_BOTH
l.size_hint_align = FILL_BOTH
l.min_max = minim, maxim
l.value = low
self.pack_end(l)
l.show()
h = self.listenhigh = Spinner(parent)
h.size_hint_weight = EXPAND_BOTH
h.size_hint_align = FILL_BOTH
h.min_max = minim, maxim
h.value = high
self.pack_end(h)
h.show()
class FsButton(Fileselector, FileselectorButton):
def __init__(self, parent, *args, **kwargs):
FileselectorButton.__init__(self, parent, *args, **kwargs)