├── .gitignore ├── README.md ├── elmextensions ├── SearchableList.py ├── StandardButton.py ├── StandardPopup.py ├── __init__.py ├── aboutwindow.py ├── easythreading.py ├── embeddedterminal.py ├── fileselector.py ├── sortedlist.py └── tabbedbox.py ├── license.txt ├── setup.py ├── sortedlistother ├── sortedgenlist.py ├── sortedlist.py └── test_sortedlist.py ├── test_aboutwindow.py ├── test_embeddedterminal.py ├── test_fileselector.py ├── test_searchablelist.py ├── test_sortedlist.py └── test_tabbedbox.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[co] 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | A library that contains a few more complex elementary objects for easy importing/usage. 2 | 3 | Current Objects: 4 | 5 | - SortedList 6 | 7 | ![alt text](https://www.enlightenment.org/ss/e-550e481ac09c94.28813425.png "SortedList") 8 | 9 | - EmbeddedTerminal 10 | 11 | ![alt text](https://www.enlightenment.org/ss/e-54ca23811cf6e3.06249212.png "EmbeddedTerminal") 12 | 13 | - AboutWindow 14 | 15 | ![alt text](http://www.enlightenment.org/ss/e-54cc6a63664aa5.62469556.png "AboutWindow") 16 | 17 | - FileSelector 18 | 19 | ![alt text](https://www.enlightenment.org/ss/e-550e47faa29009.96396030.png "FileSelector") 20 | 21 | - TabbedBox 22 | 23 | ![alt text](https://www.enlightenment.org/ss/e-550e47dbb37520.39053036.png "TabbedBox") 24 | 25 | - SearchableList 26 | 27 | ![alt text](http://www.enlightenment.org/ss/e-563689363f47b8.13740731.jpg "SearchableList") 28 | 29 | Credits: 30 | - [Jeff Hoogland](http://www.jeffhoogland.com/) 31 | - [Kai Huuhko](https://github.com/kaihu) 32 | - [Wolfgang Morawetz](https://github.com/wfx/) 33 | -------------------------------------------------------------------------------- /elmextensions/SearchableList.py: -------------------------------------------------------------------------------- 1 | try: 2 | import sys 3 | reload(sys) 4 | sys.setdefaultencoding('utf8') 5 | except NameError: 6 | pass 7 | 8 | from efl.elementary.box import Box 9 | from efl.elementary.frame import Frame 10 | from efl.elementary.button import Button 11 | from efl.elementary.entry import Entry 12 | from efl.elementary.list import List 13 | from efl.evas import EVAS_HINT_EXPAND, EVAS_HINT_FILL 14 | 15 | EXPAND_BOTH = EVAS_HINT_EXPAND, EVAS_HINT_EXPAND 16 | EXPAND_HORIZ = EVAS_HINT_EXPAND, 0.0 17 | FILL_BOTH = EVAS_HINT_FILL, EVAS_HINT_FILL 18 | FILL_HORIZ = EVAS_HINT_FILL, 0.5 19 | ALIGN_CENTER = 0.5, 0.5 20 | 21 | def searchList(text, lst): 22 | for item in lst: 23 | if text.lower() in item.lower()[:len(text)]: 24 | return lst.index(item) 25 | return 0 26 | 27 | class SearchableList(Box): 28 | def __init__(self, parent_widget, *args, **kwargs): 29 | Box.__init__(self, parent_widget, *args, **kwargs) 30 | 31 | self.ourList = ourList = List(self, size_hint_weight=EXPAND_BOTH, size_hint_align=FILL_BOTH) 32 | 33 | self.keys = [] 34 | 35 | ourList.go() 36 | ourList.show() 37 | 38 | self.ourItems = [] 39 | 40 | sframe = Frame(self, size_hint_weight=EXPAND_HORIZ, size_hint_align=FILL_HORIZ) 41 | sframe.text = "Search" 42 | self.search = search = Entry(self) 43 | search.single_line = True 44 | search.callback_changed_add(self.searchChange) 45 | sframe.content = search 46 | search.show() 47 | sframe.show() 48 | 49 | self.pack_end(ourList) 50 | self.pack_end(sframe) 51 | 52 | def callback_item_focused_add( self, ourCB ): 53 | self.ourList.callback_item_focused_add( ourCB ) 54 | 55 | def callback_clicked_double_add( self, ourCB ): 56 | self.ourList.callback_clicked_double_add( ourCB ) 57 | 58 | def item_append( self, text, ourIcon=None ): 59 | self.keys.append(text) 60 | self.keys.sort() 61 | 62 | itemSpot = self.keys.index(text) 63 | 64 | if not len(self.ourItems) or itemSpot > len(self.ourItems)-1: 65 | item = self.ourList.item_append(text, icon=ourIcon) 66 | self.ourItems.append(item) 67 | else: 68 | #print("Inserting after item %s"%self.ourItems[itemSpot]) 69 | item = self.ourList.item_insert_before(self.ourItems[itemSpot], text, icon=ourIcon) 70 | self.ourItems.insert(itemSpot, item) 71 | 72 | return item 73 | 74 | def items_get( self ): 75 | return self.ourList.items_get() 76 | 77 | def selected_item_get( self ): 78 | return self.ourList.selected_item_get() 79 | 80 | def searchChange( self, entry ): 81 | #print entry.text 82 | zeindex = searchList(entry.text, self.keys) 83 | self.ourItems[zeindex].selected_set(True) 84 | self.ourItems[zeindex].bring_in() 85 | self.search.focus = True 86 | -------------------------------------------------------------------------------- /elmextensions/StandardButton.py: -------------------------------------------------------------------------------- 1 | from efl.evas import EVAS_HINT_EXPAND, EVAS_HINT_FILL 2 | from efl import elementary 3 | from efl.elementary.button import Button 4 | from efl.elementary.box import Box 5 | from efl.elementary.icon import Icon 6 | 7 | EXPAND_BOTH = EVAS_HINT_EXPAND, EVAS_HINT_EXPAND 8 | EXPAND_HORIZ = EVAS_HINT_EXPAND, 0.0 9 | FILL_BOTH = EVAS_HINT_FILL, EVAS_HINT_FILL 10 | 11 | class StandardButton(Button): 12 | def __init__(self, ourParent, ourText, ourIcon=None, ourCB=None, *args, **kwargs): 13 | Button.__init__(self, ourParent, *args, **kwargs) 14 | icon = Icon(self, size_hint_weight=EXPAND_BOTH, size_hint_align=FILL_BOTH) 15 | icon.standard_set(ourIcon) 16 | icon.show() 17 | 18 | self.text = ourText 19 | self.content_set(icon) 20 | self.callback_clicked_add(ourCB) 21 | -------------------------------------------------------------------------------- /elmextensions/StandardPopup.py: -------------------------------------------------------------------------------- 1 | #Borrowed from ePad error popup done by ylee 2 | 3 | from efl.evas import EVAS_HINT_EXPAND, EVAS_HINT_FILL 4 | from efl import elementary 5 | from efl.elementary.box import Box 6 | from efl.elementary.icon import Icon 7 | from efl.elementary.button import Button 8 | from efl.elementary.image import Image 9 | from efl.elementary.popup import Popup 10 | from efl.elementary.label import Label, ELM_WRAP_WORD 11 | from efl.elementary.table import Table 12 | from efl.elementary.need import need_ethumb 13 | 14 | EXPAND_BOTH = EVAS_HINT_EXPAND, EVAS_HINT_EXPAND 15 | EXPAND_HORIZ = EVAS_HINT_EXPAND, 0.0 16 | FILL_BOTH = EVAS_HINT_FILL, EVAS_HINT_FILL 17 | FILL_HORIZ = EVAS_HINT_FILL, 0.5 18 | ALIGN_CENTER = 0.5, 0.5 19 | 20 | class StandardPopup(Popup): 21 | def __init__(self, ourParent, ourMsg, ourIcon=None, *args, **kwargs): 22 | Popup.__init__(self, ourParent, *args, **kwargs) 23 | self.callback_block_clicked_add(lambda obj: self.delete()) 24 | 25 | # Add a table to hold dialog image and text to Popup 26 | tb = Table(self, size_hint_weight=EXPAND_BOTH) 27 | self.part_content_set("default", tb) 28 | tb.show() 29 | 30 | # Add dialog-error Image to table 31 | need_ethumb() 32 | icon = Icon(self, thumb='True') 33 | icon.standard_set(ourIcon) 34 | # Using gksudo or sudo fails to load Image here 35 | # unless options specify using preserving their existing environment. 36 | # may also fail to load other icons but does not raise an exception 37 | # in that situation. 38 | # Works fine using eSudo as a gksudo alternative, 39 | # other alternatives not tested 40 | try: 41 | dialogImage = Image(self, 42 | size_hint_weight=EXPAND_HORIZ, 43 | size_hint_align=FILL_BOTH, 44 | file=icon.file_get()) 45 | tb.pack(dialogImage, 0, 0, 1, 1) 46 | dialogImage.show() 47 | except RuntimeError: 48 | # An error message is displayed for this same error 49 | # when aboutWin is initialized so no need to redisplay. 50 | pass 51 | # Add dialog text to table 52 | dialogLabel = Label(self, line_wrap=ELM_WRAP_WORD, 53 | size_hint_weight=EXPAND_HORIZ, 54 | size_hint_align=FILL_BOTH) 55 | dialogLabel.text = ourMsg 56 | tb.pack(dialogLabel, 1, 0, 1, 1) 57 | dialogLabel.show() 58 | 59 | # Ok Button 60 | ok_btt = Button(self) 61 | ok_btt.text = "Ok" 62 | ok_btt.callback_clicked_add(lambda obj: self.delete()) 63 | ok_btt.show() 64 | 65 | # add button to popup 66 | self.part_content_set("button3", ok_btt) 67 | -------------------------------------------------------------------------------- /elmextensions/__init__.py: -------------------------------------------------------------------------------- 1 | from .sortedlist import * 2 | from .embeddedterminal import * 3 | from .aboutwindow import * 4 | from .fileselector import * 5 | from .tabbedbox import * 6 | from .StandardButton import * 7 | from .StandardPopup import * 8 | from .SearchableList import * 9 | 10 | __copyright__ = "Copyright 2015-2017 Jeff Hoogland" 11 | __license__ = "BSD-3-clause" 12 | 13 | # the version number: major, minor, micro, releaselevel, and serial. 14 | __version__ = "0.2.1rc.2" 15 | version_string = __version__ 16 | -------------------------------------------------------------------------------- /elmextensions/aboutwindow.py: -------------------------------------------------------------------------------- 1 | from efl.ecore import Exe 2 | from efl.evas import EVAS_HINT_EXPAND, EVAS_HINT_FILL 3 | from efl import elementary 4 | from efl.elementary.window import Window, ELM_WIN_DIALOG_BASIC 5 | from efl.elementary.background import Background 6 | from efl.elementary.box import Box 7 | from efl.elementary.button import Button 8 | from efl.elementary.label import Label, ELM_WRAP_WORD 9 | from efl.elementary.icon import Icon 10 | from efl.elementary.separator import Separator 11 | from efl.elementary.frame import Frame 12 | from efl.elementary.entry import Entry, ELM_TEXT_FORMAT_PLAIN_UTF8, \ 13 | ELM_WRAP_NONE, ELM_WRAP_MIXED 14 | from efl.evas import EVAS_HINT_EXPAND, EVAS_HINT_FILL 15 | 16 | EXPAND_BOTH = EVAS_HINT_EXPAND, EVAS_HINT_EXPAND 17 | EXPAND_HORIZ = EVAS_HINT_EXPAND, 0.0 18 | EXPAND_VERT = 0.0, EVAS_HINT_EXPAND 19 | FILL_BOTH = EVAS_HINT_FILL, EVAS_HINT_FILL 20 | FILL_HORIZ = EVAS_HINT_FILL, 0.5 21 | FILL_VERT = 0.5, EVAS_HINT_FILL 22 | 23 | def xdg_open(url_or_file): 24 | Exe('xdg-open "%s"' % url_or_file) 25 | 26 | class InstanceError(Exception): 27 | pass 28 | 29 | class AboutWindow(Window): 30 | __initialized = False 31 | 32 | def __init__(self, parent, title="About", standardicon="dialog-information", \ 33 | version="N/A", authors="No One", \ 34 | licen="GPL", webaddress="", info="Something, something, turtles"): 35 | 36 | if AboutWindow.__initialized: 37 | raise InstanceError("You can't create more than 1 instance of AboutWindow") 38 | AboutWindow.__initialized = True 39 | 40 | Window.__init__(self, title, ELM_WIN_DIALOG_BASIC, autodel=True) 41 | self.callback_delete_request_add(self.close_inst) 42 | background = Background(self, size_hint_weight=EXPAND_BOTH) 43 | self.resize_object_add(background) 44 | background.show() 45 | 46 | fr = Frame(self, style='pad_large', size_hint_weight=EXPAND_BOTH, 47 | size_hint_align=FILL_BOTH) 48 | self.resize_object_add(fr) 49 | fr.show() 50 | 51 | hbox = Box(self, horizontal=True, padding=(12,12)) 52 | fr.content = hbox 53 | hbox.show() 54 | 55 | vbox = Box(self, align=(0.0,0.0), padding=(6,6), 56 | size_hint_weight=EXPAND_VERT, size_hint_align=FILL_VERT) 57 | hbox.pack_end(vbox) 58 | vbox.show() 59 | 60 | # icon + version 61 | ic = Icon(self, size_hint_min=(64,64)) 62 | ic.standard_set(standardicon) 63 | vbox.pack_end(ic) 64 | ic.show() 65 | 66 | lb = Label(self, text=('Version: %s') % version) 67 | vbox.pack_end(lb) 68 | lb.show() 69 | 70 | sep = Separator(self, horizontal=True) 71 | vbox.pack_end(sep) 72 | sep.show() 73 | 74 | # buttons 75 | bt = Button(self, text=(title), size_hint_align=FILL_HORIZ) 76 | bt.callback_clicked_add(lambda b: self.entry.text_set(info)) 77 | vbox.pack_end(bt) 78 | bt.show() 79 | 80 | bt = Button(self, text=('Website'),size_hint_align=FILL_HORIZ) 81 | bt.callback_clicked_add(lambda b: xdg_open(webaddress)) 82 | vbox.pack_end(bt) 83 | bt.show() 84 | 85 | bt = Button(self, text=('Authors'), size_hint_align=FILL_HORIZ) 86 | bt.callback_clicked_add(lambda b: self.entry.text_set(authors)) 87 | vbox.pack_end(bt) 88 | bt.show() 89 | 90 | bt = Button(self, text=('License'), size_hint_align=FILL_HORIZ) 91 | bt.callback_clicked_add(lambda b: self.entry.text_set(licen)) 92 | vbox.pack_end(bt) 93 | bt.show() 94 | 95 | # main text 96 | self.entry = Entry(self, editable=False, scrollable=True, text=info, 97 | size_hint_weight=EXPAND_BOTH, size_hint_align=FILL_BOTH) 98 | self.entry.callback_anchor_clicked_add(lambda e,i: xdg_open(i.name)) 99 | hbox.pack_end(self.entry) 100 | self.entry.show() 101 | 102 | self.resize(400, 200) 103 | self.show() 104 | 105 | def close_inst(self, obj): 106 | AboutWindow.__initialized = False 107 | -------------------------------------------------------------------------------- /elmextensions/easythreading.py: -------------------------------------------------------------------------------- 1 | from efl import ecore 2 | 3 | import threading 4 | try: 5 | import Queue 6 | except: 7 | import queue as Queue 8 | 9 | class ThreadedFunction(object): 10 | def __init__(self, doneCB=None): 11 | # private stuff 12 | self._commandQueue = Queue.Queue() 13 | self._replyQueue = Queue.Queue() 14 | self._doneCB = doneCB 15 | 16 | # add a timer to check the data returned by the worker thread 17 | self._timer = ecore.Timer(0.1, self.checkReplyQueue) 18 | 19 | # start the working thread 20 | threading.Thread(target=self.threadFunc).start() 21 | 22 | def run(self, action): 23 | self._commandQueue.put(action) 24 | 25 | def shutdown(self): 26 | self._timer.delete() 27 | self._commandQueue.put('QUIT') 28 | 29 | def checkReplyQueue(self): 30 | if not self._replyQueue.empty(): 31 | result = self._replyQueue.get() 32 | if callable(self._doneCB): 33 | self._doneCB() 34 | return True 35 | 36 | # all the member below this point run in the thread 37 | def threadFunc(self): 38 | while True: 39 | # wait here until an item in the queue is present 40 | func = self._commandQueue.get() 41 | if callable(func): 42 | func() 43 | elif func == 'QUIT': 44 | break 45 | self._replyQueue.put("done") 46 | -------------------------------------------------------------------------------- /elmextensions/embeddedterminal.py: -------------------------------------------------------------------------------- 1 | from efl import ecore 2 | from efl.elementary.box import Box 3 | from efl.elementary.frame import Frame 4 | from efl.elementary.button import Button 5 | from efl.elementary.entry import Entry, markup_to_utf8 6 | from efl.evas import EVAS_HINT_EXPAND, EVAS_HINT_FILL 7 | 8 | EXPAND_BOTH = EVAS_HINT_EXPAND, EVAS_HINT_EXPAND 9 | EXPAND_HORIZ = EVAS_HINT_EXPAND, 0.0 10 | FILL_BOTH = EVAS_HINT_FILL, EVAS_HINT_FILL 11 | FILL_HORIZ = EVAS_HINT_FILL, 0.5 12 | 13 | class EmbeddedTerminal(Box): 14 | def __init__(self, parent_widget, titles=None, *args, **kwargs): 15 | Box.__init__(self, parent_widget, *args, **kwargs) 16 | 17 | self.outPut = Entry(self, size_hint_weight=EXPAND_BOTH, size_hint_align=FILL_BOTH) 18 | self.outPut.editable_set(False) 19 | self.outPut.scrollable_set(True) 20 | self.outPut.callback_changed_add(self.changedCb) 21 | self.outPut.show() 22 | 23 | frame = Frame(self, size_hint_weight=EXPAND_HORIZ, size_hint_align=FILL_HORIZ) 24 | frame.text = "Input:" 25 | frame.autocollapse_set(True) 26 | frame.collapse_go(True) 27 | frame.show() 28 | 29 | bx = Box(self, size_hint_weight=EXPAND_HORIZ, size_hint_align=FILL_HORIZ) 30 | bx.horizontal = True 31 | bx.show() 32 | 33 | frame.content = bx 34 | 35 | self.inPut = Entry(self, size_hint_weight=EXPAND_BOTH, size_hint_align=FILL_BOTH) 36 | self.inPut.single_line_set(True) 37 | self.inPut.callback_activated_add(self.enterPressed) 38 | self.inPut.show() 39 | 40 | enterButton = Button(self) 41 | enterButton.text = "Execute" 42 | enterButton.callback_pressed_add(self.enterPressed) 43 | enterButton.show() 44 | 45 | bx.pack_end(self.inPut) 46 | bx.pack_end(enterButton) 47 | 48 | self.pack_end(self.outPut) 49 | self.pack_end(frame) 50 | 51 | self.cmd_exe = None 52 | self.done_cb = None 53 | 54 | def changedCb(self, obj): 55 | obj.cursor_end_set() 56 | 57 | def enterPressed(self, btn): 58 | if not self.cmd_exe: 59 | self.runCommand(self.inPut.text) 60 | self.inPut.text = "" 61 | else: 62 | ourResult = self.cmd_exe.send("%s\n"%self.inPut.text) 63 | self.inPut.text = "" 64 | 65 | def runCommand(self, command, done_cb=None): 66 | command = markup_to_utf8(command) 67 | self.cmd_exe = cmd = ecore.Exe( 68 | command, 69 | ecore.ECORE_EXE_PIPE_READ | 70 | ecore.ECORE_EXE_PIPE_ERROR | 71 | ecore.ECORE_EXE_PIPE_WRITE 72 | ) 73 | cmd.on_add_event_add(self.command_started) 74 | cmd.on_data_event_add(self.received_data) 75 | cmd.on_error_event_add(self.received_error) 76 | cmd.on_del_event_add(self.command_done) 77 | 78 | self.done_cb = done_cb 79 | 80 | def command_started(self, cmd, event, *args, **kwargs): 81 | self.outPut.entry_append("---------------------------------") 82 | self.outPut.entry_append("
") 83 | 84 | def received_data(self, cmd, event, *args, **kwargs): 85 | self.outPut.entry_append("%s"%event.data) 86 | self.outPut.entry_append("
") 87 | 88 | def received_error(self, cmd, event, *args, **kwargs): 89 | self.outPut.entry_append("Error: %s" % event.data) 90 | 91 | def command_done(self, cmd, event, *args, **kwargs): 92 | self.outPut.entry_append("---------------------------------") 93 | self.outPut.entry_append("
") 94 | self.cmd_exe = None 95 | if self.done_cb: 96 | if callable(self.done_cb): 97 | self.done_cb() 98 | -------------------------------------------------------------------------------- /elmextensions/fileselector.py: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | from efl.elementary.label import Label 4 | from efl.elementary.icon import Icon 5 | from efl.elementary.box import Box 6 | from efl.elementary.list import List 7 | from efl.elementary.genlist import Genlist, GenlistItem, GenlistItemClass, \ 8 | ELM_LIST_COMPRESS 9 | from efl.elementary.button import Button 10 | from efl.elementary.hoversel import Hoversel 11 | from efl.elementary.separator import Separator 12 | from efl.elementary.panes import Panes 13 | from efl.elementary.popup import Popup 14 | from efl.elementary.entry import Entry, ELM_INPUT_HINT_AUTO_COMPLETE 15 | from efl.elementary.image import Image 16 | from efl.evas import EVAS_HINT_EXPAND, EVAS_HINT_FILL, EVAS_CALLBACK_KEY_DOWN 17 | from efl import ecore 18 | 19 | #imported to work around a bug 20 | import efl.elementary.layout 21 | 22 | import os 23 | import math 24 | from .easythreading import ThreadedFunction 25 | from collections import deque 26 | 27 | EXPAND_BOTH = EVAS_HINT_EXPAND, EVAS_HINT_EXPAND 28 | EXPAND_HORIZ = EVAS_HINT_EXPAND, 0.0 29 | FILL_BOTH = EVAS_HINT_FILL, EVAS_HINT_FILL 30 | FILL_HORIZ = EVAS_HINT_FILL, 0.5 31 | 32 | 33 | class FileGLIC(GenlistItemClass): 34 | 35 | def text_get(self, gl, part, data): 36 | return data["d"] 37 | 38 | def content_get(self, gl, part, data): 39 | if part == "elm.swallow.icon": 40 | return Icon( 41 | gl, 42 | standard="gtk-file" 43 | ) 44 | 45 | fileglic = FileGLIC(item_style="one_icon") 46 | 47 | 48 | class DirGLIC(GenlistItemClass): 49 | 50 | def text_get(self, gl, part, data): 51 | return data["d"] 52 | 53 | def content_get(self, gl, part, data): 54 | if part == "elm.swallow.icon": 55 | return Icon( 56 | gl, 57 | standard="gtk-directory" 58 | ) 59 | 60 | dirglic = DirGLIC(item_style="one_icon") 61 | 62 | 63 | class FileSelector(Box): 64 | def __init__(self, parent_widget, defaultPath="", defaultPopulate=True, *args, **kwargs): 65 | Box.__init__(self, parent_widget, *args, **kwargs) 66 | 67 | self.cancelCallback = None 68 | self.actionCallback = None 69 | self.directoryChangeCallback = None 70 | 71 | self.threadedFunction = ThreadedFunction() 72 | self._timer = ecore.Timer(0.02, self.populateFile) 73 | 74 | #Watch key presses for ctrl+l to select entry 75 | parent_widget.elm_event_callback_add(self.eventsCb) 76 | 77 | self.selectedFolder = None 78 | self.showHidden = False 79 | self.currentDirectory = None 80 | self.focusedEntry = None 81 | self.folderOnly = False 82 | self.sortReverse = False 83 | self.addingHidden = False 84 | self.pendingFiles = deque() 85 | self.currentSubFolders = [] 86 | self.currentFiles = [] 87 | 88 | #Mode should be "save" or "load" 89 | self.mode = "save" 90 | 91 | self.home = os.path.expanduser("~") 92 | self.root = "/" 93 | 94 | #Label+Entry for File Name 95 | self.filenameBox = Box(self, size_hint_weight=EXPAND_HORIZ, 96 | size_hint_align=FILL_HORIZ) 97 | self.filenameBox.horizontal = True 98 | self.filenameBox.show() 99 | 100 | fileLabel = Label(self, size_hint_weight=(0.15, EVAS_HINT_EXPAND), 101 | size_hint_align=FILL_HORIZ) 102 | fileLabel.text = "Filename:" 103 | fileLabel.show() 104 | 105 | self.fileEntry = Entry(self, size_hint_weight=EXPAND_BOTH, 106 | size_hint_align=FILL_HORIZ) 107 | self.fileEntry.single_line_set(True) 108 | self.fileEntry.scrollable_set(True) 109 | self.fileEntry.callback_changed_user_add(self.fileEntryChanged) 110 | self.fileEntry.show() 111 | 112 | self.filenameBox.pack_end(fileLabel) 113 | self.filenameBox.pack_end(self.fileEntry) 114 | 115 | sep = Separator(self, size_hint_weight=EXPAND_HORIZ, 116 | size_hint_align=FILL_HORIZ) 117 | sep.horizontal_set(True) 118 | sep.show() 119 | 120 | #Label+Entry for File Path 121 | self.filepathBox = Box(self, size_hint_weight=EXPAND_HORIZ, 122 | size_hint_align=FILL_HORIZ) 123 | self.filepathBox.horizontal = True 124 | self.filepathBox.show() 125 | 126 | fileLabel = Label(self, size_hint_weight=(0.15, EVAS_HINT_EXPAND), 127 | size_hint_align=FILL_HORIZ) 128 | fileLabel.text = "Current Folder:" 129 | fileLabel.show() 130 | 131 | self.filepathEntry = Entry(self, size_hint_weight=EXPAND_BOTH, 132 | size_hint_align=FILL_HORIZ) 133 | self.filepathEntry.single_line_set(True) 134 | self.filepathEntry.scrollable_set(True) 135 | self.filepathEntry.callback_changed_user_add(self.fileEntryChanged) 136 | self.filepathEntry.callback_unfocused_add(self.filepathEditDone) 137 | self.filepathEntry.callback_activated_add(self.filepathEditDone) 138 | #Wish this worked. Doesn't seem to do anything 139 | #self.filepathEntry.input_hint_set(ELM_INPUT_HINT_AUTO_COMPLETE) 140 | 141 | if defaultPath and os.path.isdir(defaultPath): 142 | startPath = defaultPath 143 | else: 144 | startPath = self.home 145 | self.filepathEntry.show() 146 | 147 | self.filepathBox.pack_end(fileLabel) 148 | self.filepathBox.pack_end(self.filepathEntry) 149 | 150 | self.autocompleteHover = Hoversel(self, hover_parent=self) 151 | self.autocompleteHover.callback_selected_add(self.autocompleteSelected) 152 | #self.autocompleteHover.show() 153 | 154 | self.fileSelectorBox = Panes(self, content_left_size=0.3, 155 | size_hint_weight=EXPAND_BOTH, size_hint_align=FILL_BOTH) 156 | self.fileSelectorBox.show() 157 | 158 | """Bookmarks Box contains: 159 | 160 | - Button - Up Arrow 161 | - List - Home/Root/GTK bookmarks 162 | - Box 163 | -- Button - Add Bookmark 164 | -- Button - Remove Bookmark""" 165 | self.bookmarkBox = Box(self, size_hint_weight=(0.3, EVAS_HINT_EXPAND), 166 | size_hint_align=FILL_BOTH) 167 | self.bookmarkBox.show() 168 | 169 | 170 | upIcon = Icon(self, size_hint_weight=EXPAND_BOTH, 171 | size_hint_align=FILL_BOTH) 172 | upIcon.standard_set("go-up") 173 | upIcon.show() 174 | 175 | self.upButton = Button(self, size_hint_weight=EXPAND_HORIZ, 176 | size_hint_align=FILL_HORIZ, content=upIcon) 177 | self.upButton.text = "Up" 178 | self.upButton.callback_pressed_add(self.upButtonPressed) 179 | self.upButton.show() 180 | 181 | self.bookmarksList = List(self, size_hint_weight=EXPAND_BOTH, 182 | size_hint_align=FILL_BOTH) 183 | self.bookmarksList.callback_activated_add(self.bookmarkDoubleClicked) 184 | self.bookmarksList.show() 185 | 186 | self.bookmarkModBox = Box(self, size_hint_weight=EXPAND_HORIZ, 187 | size_hint_align=FILL_HORIZ) 188 | self.bookmarkModBox.horizontal = True 189 | self.bookmarkModBox.show() 190 | 191 | con = Icon(self, size_hint_weight=EXPAND_BOTH, 192 | size_hint_align=FILL_BOTH) 193 | con.standard_set("add") 194 | con.show() 195 | 196 | self.addButton = Button(self, size_hint_weight=EXPAND_HORIZ, 197 | size_hint_align=FILL_HORIZ, content=con) 198 | self.addButton.callback_pressed_add(self.addButtonPressed) 199 | self.addButton.disabled = True 200 | self.addButton.show() 201 | 202 | con = Icon(self, size_hint_weight=EXPAND_BOTH, 203 | size_hint_align=FILL_BOTH) 204 | con.standard_set("remove") 205 | con.show() 206 | 207 | self.removeButton = Button(self, size_hint_weight=EXPAND_HORIZ, 208 | size_hint_align=FILL_HORIZ, content=con) 209 | self.removeButton.callback_pressed_add(self.removeButtonPressed) 210 | self.removeButton.disabled = True 211 | self.removeButton.show() 212 | 213 | self.bookmarkModBox.pack_end(self.addButton) 214 | self.bookmarkModBox.pack_end(self.removeButton) 215 | 216 | self.bookmarkBox.pack_end(self.upButton) 217 | self.bookmarkBox.pack_end(self.bookmarksList) 218 | self.bookmarkBox.pack_end(self.bookmarkModBox) 219 | 220 | #Directory List 221 | self.fileListBox = Box(self, size_hint_weight=EXPAND_BOTH, 222 | size_hint_align=FILL_BOTH) 223 | self.fileListBox.show() 224 | 225 | self.fileSortButton = Button(self, size_hint_weight=EXPAND_HORIZ, 226 | size_hint_align=FILL_HORIZ) 227 | self.fileSortButton.text = u"⬆ Name" 228 | self.fileSortButton.callback_pressed_add(self.sortData) 229 | self.fileSortButton.show() 230 | 231 | self.fileList = Genlist(self, size_hint_weight=EXPAND_BOTH, 232 | size_hint_align=FILL_BOTH, homogeneous=True, 233 | mode=ELM_LIST_COMPRESS) 234 | self.fileList.callback_activated_add(self.fileDoubleClicked) 235 | self.fileList.show() 236 | 237 | self.previewImage = previewImage = Image(self) 238 | #previewImage.size_hint_weight = EXPAND_BOTH 239 | previewImage.size_hint_align = FILL_BOTH 240 | previewImage.show() 241 | 242 | self.fileListBox.pack_end(self.fileSortButton) 243 | self.fileListBox.pack_end(self.fileList) 244 | self.fileListBox.pack_end(self.previewImage) 245 | 246 | self.fileSelectorBox.part_content_set("left", self.bookmarkBox) 247 | self.fileSelectorBox.part_content_set("right", self.fileListBox) 248 | 249 | #Cancel and Save/Open button 250 | self.buttonBox = Box(self, size_hint_weight=EXPAND_HORIZ, 251 | size_hint_align=(1.0, 0.5)) 252 | self.buttonBox.horizontal = True 253 | self.buttonBox.show() 254 | 255 | self.actionIcon = Icon(self, size_hint_weight=EXPAND_BOTH, 256 | size_hint_align=FILL_BOTH) 257 | self.actionIcon.standard_set("document-save") 258 | self.actionIcon.show() 259 | 260 | self.actionButton = Button(self, size_hint_weight=(0.0, 0.0), 261 | size_hint_align=(1.0, 0.5), content=self.actionIcon) 262 | self.actionButton.text = "Save " 263 | self.actionButton.callback_pressed_add(self.actionButtonPressed) 264 | self.actionButton.show() 265 | 266 | cancelIcon = Icon(self, size_hint_weight=EXPAND_BOTH, 267 | size_hint_align=FILL_BOTH) 268 | cancelIcon.standard_set("exit") 269 | cancelIcon.show() 270 | 271 | self.cancelButton = Button(self, size_hint_weight=(0.0, 0.0), 272 | size_hint_align=(1.0, 0.5), content=cancelIcon) 273 | self.cancelButton.text = "Cancel " 274 | self.cancelButton.callback_pressed_add(self.cancelButtonPressed) 275 | self.cancelButton.show() 276 | 277 | con = Icon(self, size_hint_weight=EXPAND_BOTH, 278 | size_hint_align=FILL_BOTH) 279 | con.standard_set("gtk-find") 280 | con.show() 281 | 282 | self.toggleHiddenButton = Button(self, size_hint_weight=(0.0, 0.0), 283 | size_hint_align=(1.0, 0.5), content=con) 284 | self.toggleHiddenButton.text = "Toggle Hidden " 285 | self.toggleHiddenButton.callback_pressed_add(self.toggleHiddenButtonPressed) 286 | self.toggleHiddenButton.show() 287 | 288 | con = Icon(self, size_hint_weight=EXPAND_BOTH, 289 | size_hint_align=FILL_BOTH) 290 | con.standard_set("folder-new") 291 | con.show() 292 | 293 | self.createFolderButton = Button(self, size_hint_weight=(0.0, 0.0), 294 | size_hint_align=(1.0, 0.5), content=con) 295 | self.createFolderButton.text = "Create Folder " 296 | self.createFolderButton.callback_pressed_add(self.createFolderButtonPressed) 297 | self.createFolderButton.show() 298 | 299 | self.buttonBox.pack_end(self.createFolderButton) 300 | self.buttonBox.pack_end(self.toggleHiddenButton) 301 | self.buttonBox.pack_end(self.cancelButton) 302 | self.buttonBox.pack_end(self.actionButton) 303 | 304 | self.pack_end(self.filenameBox) 305 | self.pack_end(sep) 306 | self.pack_end(self.filepathBox) 307 | self.pack_end(self.autocompleteHover) 308 | self.pack_end(self.fileSelectorBox) 309 | self.pack_end(self.buttonBox) 310 | 311 | self.populateBookmarks() 312 | 313 | self.createPopup = Popup(self) 314 | self.createPopup.part_text_set("title,text", "Create Folder:") 315 | 316 | self.createEn = en = Entry(self, size_hint_weight=EXPAND_HORIZ, 317 | size_hint_align=FILL_HORIZ) 318 | en.single_line_set(True) 319 | en.scrollable_set(True) 320 | en.show() 321 | 322 | self.createPopup.content = en 323 | 324 | bt = Button(self, text="Create") 325 | bt.callback_clicked_add(self.createFolder) 326 | self.createPopup.part_content_set("button1", bt) 327 | 328 | bt2 = Button(self, text="Cancel") 329 | bt2.callback_clicked_add(self.closePopup) 330 | self.createPopup.part_content_set("button2", bt2) 331 | 332 | if defaultPopulate: 333 | self.populateFiles(startPath) 334 | 335 | def folderOnlySet(self, ourValue): 336 | self.folderOnly = ourValue 337 | 338 | if not self.folderOnly: 339 | self.filenameBox.show() 340 | else: 341 | self.filenameBox.hide() 342 | 343 | def createFolder(self, obj): 344 | newDir = "%s%s"%(self.currentDirectory, self.createEn.text) 345 | os.makedirs(newDir) 346 | self.closePopup() 347 | self.populateFiles(self.currentDirectory) 348 | 349 | def createFolderButtonPressed(self, obj): 350 | self.createEn.text = "" 351 | self.createPopup.show() 352 | self.createEn.select_all() 353 | 354 | def closePopup(self, btn=None): 355 | self.createPopup.hide() 356 | 357 | def shutdown(self, obj=None): 358 | self._timer.delete() 359 | self.threadedFunction.shutdown() 360 | 361 | def sortData(self, btn): 362 | self.sortReverse = not self.sortReverse 363 | 364 | if self.sortReverse: 365 | self.fileSortButton.text = u"⬇ Name" 366 | else: 367 | self.fileSortButton.text = u"⬆ Name" 368 | 369 | self.populateFiles(self.currentDirectory) 370 | 371 | def populateBookmarks(self): 372 | con = Icon(self, size_hint_weight=EXPAND_BOTH, 373 | size_hint_align=FILL_BOTH) 374 | con.standard_set("folder_home") 375 | con.show() 376 | 377 | it = self.bookmarksList.item_append("Home", icon=con) 378 | it.data["path"] = self.home 379 | 380 | con = Icon(self, size_hint_weight=EXPAND_BOTH, 381 | size_hint_align=FILL_BOTH) 382 | con.standard_set("drive-harddisk") 383 | con.show() 384 | 385 | it = self.bookmarksList.item_append("Root", icon=con) 386 | it.data["path"] = self.root 387 | 388 | it = self.bookmarksList.item_append("") 389 | it.separator_set(True) 390 | 391 | for bk in self.getGTKBookmarks(): 392 | con = Icon(self, size_hint_weight=EXPAND_BOTH, 393 | size_hint_align=FILL_BOTH) 394 | con.standard_set("gtk-directory") 395 | con.show() 396 | it = self.bookmarksList.item_append(bk.split("/")[-1], icon=con) 397 | it.data["path"] = bk[7:] 398 | 399 | def populateFile(self): 400 | pen_file = len(self.pendingFiles) 401 | if pen_file: 402 | for _ in range(int(math.sqrt(pen_file))): 403 | ourPath, d, isDir = self.pendingFiles.popleft() 404 | self.packFileFolder(ourPath, d, isDir) 405 | 406 | #else: 407 | # self._timer.freeze() 408 | 409 | return True 410 | 411 | def populateFiles(self, ourPath): 412 | self.autocompleteHover.hover_end() 413 | 414 | self.pendingFiles.clear() 415 | 416 | if ourPath[:-1] != "/": 417 | ourPath = ourPath + "/" 418 | 419 | if ourPath != self.filepathEntry.text or not self.showHidden: 420 | self.addingHidden = False 421 | 422 | if self.directoryChangeCallback: 423 | self.directoryChangeCallback(ourPath) 424 | 425 | del self.currentSubFolders[:] 426 | del self.currentFiles[:] 427 | self.fileList.clear() 428 | else: 429 | self.addingHidden = True 430 | 431 | self.filepathEntry.text = ourPath.replace("//", "/") 432 | self.currentDirectory = ourPath.replace("//", "/") 433 | 434 | self.threadedFunction.run(self.getFolderContents) 435 | #self._timer.thaw() 436 | 437 | def getFolderContents(self): 438 | ourPath = self.currentDirectory 439 | 440 | try: 441 | data = os.listdir(unicode(ourPath)) 442 | except: 443 | data = os.listdir(str(ourPath)) 444 | 445 | sortedData = [] 446 | 447 | for d in data: 448 | isDir = os.path.isdir("%s%s"%(ourPath, d)) 449 | 450 | if isDir: 451 | self.currentSubFolders.append(d) 452 | if self.sortReverse: 453 | sortedData.append([1, d]) 454 | else: 455 | sortedData.append([0, d]) 456 | else: 457 | self.currentFiles.append(d) 458 | if self.sortReverse: 459 | sortedData.append([0, d]) 460 | else: 461 | sortedData.append([1, d]) 462 | 463 | sortedData.sort(reverse=self.sortReverse) 464 | 465 | for ourFile in sortedData: 466 | d = ourFile[1] 467 | isDir = ourFile[0] if self.sortReverse else not ourFile[0] 468 | if self.addingHidden and d[0] == ".": 469 | self.pendingFiles.append([ourPath, d, isDir]) 470 | elif (d[0] != "." or self.showHidden) and not self.addingHidden: 471 | self.pendingFiles.append([ourPath, d, isDir]) 472 | 473 | def packFileFolder(self, ourPath, d, isDir): 474 | if isDir: 475 | li = GenlistItem(item_data={"type": "dir", "path": ourPath, "d": d}, item_class=dirglic, func=self.listItemSelected) 476 | else: 477 | li = GenlistItem(item_data={"type": "file", "path": ourPath, "d": d}, item_class=fileglic, func=self.listItemSelected) 478 | 479 | li.append_to(self.fileList) 480 | #self.fileList.go() 481 | #print("Adding: %s %s %s"%(ourPath, d, isDir)) 482 | 483 | def fileDoubleClicked(self, obj, item=None, eventData=None): 484 | if item.data["type"] == "dir": 485 | self.addButton.disabled = True 486 | self.removeButton.disabled = True 487 | self.populateFiles(item.data["path"]+item.text) 488 | else: 489 | self.actionButtonPressed(self.actionButton) 490 | 491 | def getGTKBookmarks(self): 492 | try: 493 | with open(os.path.expanduser('~/.config/gtk-3.0/bookmarks'),'r') as f: 494 | ourBks = [] 495 | for x in f: 496 | x = x.split(" ")[0] 497 | x = x.replace("%20", " ") 498 | x = x.strip() 499 | ourBks.append(x) 500 | return ourBks 501 | except IOError: 502 | return [] 503 | 504 | def bookmarkDoubleClicked(self, obj, item=None, eventData=None): 505 | item.selected_set(False) 506 | self.addButton.disabled = True 507 | self.removeButton.disabled = True 508 | self.populateFiles(item.data["path"]) 509 | 510 | def listItemSelected(self, item, gl, data): 511 | if item.data["type"] == "dir": 512 | self.directorySelected(item) 513 | else: 514 | self.fileSelected(item.text) 515 | item.selected_set(False) 516 | 517 | def fileSelected(self, ourFile): 518 | self.fileEntry.text = ourFile 519 | self.addButton.disabled = True 520 | self.removeButton.disabled = True 521 | self.selectedFolder = None 522 | 523 | #Update image preview if an image is selected 524 | if ourFile[-3:] in ["jpg", "png", "gif"]: 525 | self.previewImage.file_set("%s/%s"%(self.filepathEntry.text, ourFile)) 526 | self.previewImage.size_hint_weight = (1.0, 0.4) 527 | else: 528 | self.previewImage.size_hint_weight = (0, 0) 529 | 530 | def directorySelected(self, btn): 531 | ourPath = btn.data["path"] 532 | if btn == self.selectedFolder: 533 | self.populateFiles(ourPath) 534 | self.addButton.disabled = True 535 | else: 536 | self.selectedFolder = btn 537 | 538 | currentMarks = self.getGTKBookmarks() 539 | 540 | toAppend = "file://%s%s"%(self.filepathEntry.text, self.selectedFolder.text) 541 | 542 | if toAppend not in currentMarks: 543 | self.addButton.disabled = False 544 | self.removeButton.disabled = True 545 | else: 546 | self.addButton.disabled = True 547 | self.removeButton.disabled = False 548 | 549 | def upButtonPressed(self, btn): 550 | ourSplit = self.filepathEntry.text.split("/") 551 | del ourSplit[-1] 552 | del ourSplit[-1] 553 | self.populateFiles("/".join(ourSplit)) 554 | 555 | def addButtonPressed(self, btn): 556 | toAppend = "file://%s%s"%(self.filepathEntry.text, self.selectedFolder.text.replace(" ", "%20")) 557 | 558 | con = Icon(self, size_hint_weight=EXPAND_BOTH, 559 | size_hint_align=FILL_BOTH) 560 | con.standard_set("gtk-directory") 561 | con.show() 562 | it = self.bookmarksList.item_append(self.selectedFolder.text, icon=con) 563 | it.data["path"] = "%s%s"%(self.filepathEntry.text, self.selectedFolder.text) 564 | 565 | self.bookmarksList.go() 566 | 567 | self.addButton.disabled = True 568 | self.removeButton.disabled = False 569 | 570 | with open(os.path.expanduser('~/.config/gtk-3.0/bookmarks'),'a') as f: 571 | f.write( toAppend + " " + self.selectedFolder.text + "\n" ) 572 | 573 | def removeButtonPressed(self, btn): 574 | toRemove = "file://%s%s"%(self.filepathEntry.text, self.selectedFolder.text) 575 | 576 | bks = self.getGTKBookmarks() 577 | bks.remove(toRemove) 578 | 579 | with open(os.path.expanduser('~/.config/gtk-3.0/bookmarks'),'w') as f: 580 | for b in bks: 581 | bName = b.split("/")[-1] 582 | b = b.replace(" ", "%20") 583 | f.write( b + " " + bName + "\n" ) 584 | 585 | self.bookmarksList.clear() 586 | self.populateBookmarks() 587 | 588 | self.addButton.disabled = False 589 | self.removeButton.disabled = True 590 | 591 | def setMode(self, ourMode): 592 | self.mode = ourMode.lower() 593 | 594 | self.actionButton.text = "%s "%ourMode 595 | self.actionIcon.standard_set("document-%s"%ourMode.lower()) 596 | 597 | if self.mode != "save": 598 | self.createFolderButton.hide() 599 | else: 600 | self.createFolderButton.show() 601 | 602 | def eventsCb(self, obj, src, event_type, event): 603 | if event.modifier_is_set("Control") and event_type == EVAS_CALLBACK_KEY_DOWN: 604 | if event.key.lower() == "l": 605 | self.filepathEntry.focus_set(True) 606 | self.filepathEntry.cursor_end_set() 607 | 608 | def toggleHiddenButtonPressed(self, btn): 609 | self.showHidden = not self.showHidden 610 | self.populateFiles(self.filepathEntry.text) 611 | 612 | def toggleHidden(self): 613 | self.showHidden = not self.showHidden 614 | self.populateFiles(self.filepathEntry.text) 615 | 616 | def callback_cancel_add(self, cb): 617 | self.cancelCallback = cb 618 | 619 | def callback_activated_add(self, cb): 620 | self.actionCallback = cb 621 | 622 | def callback_directory_open_add(self, cb): 623 | self.directoryChangeCallback = cb 624 | 625 | def cancelButtonPressed(self, btn): 626 | if self.cancelCallback: 627 | self.cancelCallback(self) 628 | 629 | def actionButtonPressed(self, btn): 630 | if self.actionCallback: 631 | if not self.folderOnly and self.fileEntry.text: 632 | self.actionCallback(self, "%s%s"%(self.filepathEntry.text, self.fileEntry.text)) 633 | elif self.folderOnly: 634 | self.actionCallback(self, "%s"%(self.filepathEntry.text)) 635 | 636 | def fileEntryChanged(self, en): 637 | typed = en.text.split("/")[-1] 638 | 639 | newList = [] 640 | 641 | self.focusedEntry = en 642 | 643 | if en == self.filepathEntry: 644 | for x in self.currentSubFolders: 645 | if typed in x: 646 | if len(newList) < 10: 647 | newList.append(x) 648 | else: 649 | break 650 | else: 651 | for x in self.currentFiles: 652 | if typed in x: 653 | if len(newList) < 10: 654 | newList.append(x) 655 | else: 656 | break 657 | 658 | if self.autocompleteHover.expanded_get(): 659 | self.autocompleteHover.hover_end() 660 | 661 | self.autocompleteHover.clear() 662 | 663 | for x in newList: 664 | self.autocompleteHover.item_add(x) 665 | 666 | self.autocompleteHover.hover_begin() 667 | 668 | def autocompleteSelected(self, hov, item): 669 | hov.hover_end() 670 | if self.focusedEntry == self.filepathEntry: 671 | self.populateFiles("%s%s"%(self.currentDirectory, item.text)) 672 | self.filepathEntry.cursor_end_set() 673 | else: 674 | self.fileEntry.text = item.text 675 | self.fileEntry.cursor_end_set() 676 | 677 | def filepathEditDone(self, en): 678 | if os.path.isdir(en.text) and en.text != self.currentDirectory: 679 | self.populateFiles(en.text) 680 | self.filepathEntry.cursor_end_set() 681 | else: 682 | #en.text = self.currentDirectory 683 | pass 684 | 685 | def selected_get(self): 686 | return "%s%s"%(self.filepathEntry.text, self.fileEntry.text) 687 | -------------------------------------------------------------------------------- /elmextensions/sortedlist.py: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | from efl.elementary.label import Label 4 | from efl.elementary.box import Box 5 | #from efl.elementary.table import Table 6 | from efl.elementary.panes import Panes 7 | from efl.elementary.button import Button 8 | from efl.elementary.scroller import Scroller, Scrollable, ELM_SCROLLER_POLICY_OFF, ELM_SCROLLER_POLICY_ON, ELM_SCROLLER_POLICY_AUTO 9 | from efl.evas import EVAS_HINT_EXPAND, EVAS_HINT_FILL 10 | 11 | EXPAND_BOTH = EVAS_HINT_EXPAND, EVAS_HINT_EXPAND 12 | EXPAND_HORIZ = EVAS_HINT_EXPAND, 0.0 13 | FILL_BOTH = EVAS_HINT_FILL, EVAS_HINT_FILL 14 | FILL_HORIZ = EVAS_HINT_FILL, 0.5 15 | 16 | class SortedList(Scroller): 17 | 18 | """ 19 | A "spread sheet like" widget for elementary. 20 | Argument "titles" is a list, with each element being a tuple: 21 | (, ) 22 | """ 23 | 24 | def __init__(self, parent_widget, titles=None, initial_sort=0, 25 | ascending=True, *args, **kwargs): 26 | Scroller.__init__(self, parent_widget, *args, **kwargs) 27 | self.policy_set(ELM_SCROLLER_POLICY_AUTO, ELM_SCROLLER_POLICY_OFF) 28 | 29 | self.mainBox = Box(self, size_hint_weight=EXPAND_BOTH, 30 | size_hint_align=FILL_BOTH) 31 | self.mainBox.show() 32 | 33 | self.header = titles 34 | self.sort_column = initial_sort 35 | self.sort_column_ascending = ascending 36 | 37 | self.rows = [] 38 | self.header_row = [] 39 | 40 | headerPane = Panes(self, size_hint_weight=EXPAND_HORIZ, 41 | size_hint_align=FILL_HORIZ) 42 | headerPane.callback_unpress_add(self.paneResized) 43 | headerPane.show() 44 | 45 | listPane = Panes(self, size_hint_weight=EXPAND_BOTH, 46 | size_hint_align=FILL_BOTH) 47 | listPane.callback_unpress_add(self.paneResized) 48 | listPane.style_set("flush") 49 | listPane.show() 50 | 51 | headerPane.data["related"] = listPane 52 | listPane.data["related"] = headerPane 53 | 54 | self.mainScr = Scroller(self, size_hint_weight=EXPAND_BOTH, 55 | size_hint_align=FILL_BOTH) 56 | self.mainScr.policy_set(ELM_SCROLLER_POLICY_OFF, ELM_SCROLLER_POLICY_AUTO) 57 | self.mainScr.content = listPane 58 | self.mainScr.show() 59 | 60 | self.headerPanes = [] 61 | self.headerPanes.append(headerPane) 62 | self.listPanes = [] 63 | self.listPanes.append(listPane) 64 | self.lists = [] 65 | 66 | #self.pack_end(self.header_box) 67 | 68 | if titles is not None: 69 | self.header_row_pack(titles) 70 | 71 | self.mainBox.pack_end(headerPane) 72 | self.mainBox.pack_end(self.mainScr) 73 | 74 | self.content = self.mainBox 75 | self.show() 76 | 77 | def header_row_pack(self, titles): 78 | 79 | """Takes a list (or a tuple) of tuples (string, bool, int) and packs them to 80 | the first row of the table.""" 81 | 82 | assert isinstance(titles, (list, tuple)) 83 | for t in titles: 84 | assert isinstance(t, tuple) 85 | assert len(t) == 2 86 | title, sortable = t 87 | try: 88 | assert isinstance(title, basestring) 89 | except: 90 | assert isinstance(title, str) 91 | assert isinstance(sortable, bool) 92 | 93 | def sort_btn_cb(button, col): 94 | if self.sort_column == col: 95 | self.reverse() 96 | else: 97 | self.sort_by_column(col) 98 | 99 | titleCount = len(titles) 100 | for count, t in enumerate(titles): 101 | title, sortable = t 102 | btn = Button(self, size_hint_weight=EXPAND_HORIZ, 103 | size_hint_align=FILL_HORIZ, text=title) 104 | btn.callback_clicked_add(sort_btn_cb, count) 105 | if not sortable: 106 | btn.disabled = True 107 | btn.show() 108 | self.header_row.append(btn) 109 | 110 | bx = Box(self, size_hint_weight=EXPAND_BOTH, 111 | size_hint_align=FILL_BOTH) 112 | bx.show() 113 | 114 | if len(self.listPanes) < titleCount: 115 | wdth = 1.0 / (titleCount - count) 116 | self.listPanes[count].part_content_set("left", bx) 117 | self.listPanes[count].content_left_size = wdth 118 | 119 | nextList = Panes(self, size_hint_weight=EXPAND_BOTH, 120 | size_hint_align=FILL_BOTH) 121 | nextList.callback_unpress_add(self.paneResized) 122 | nextList.style_set("flush") 123 | nextList.show() 124 | 125 | self.listPanes[count].part_content_set("right", nextList) 126 | self.listPanes.append(nextList) 127 | 128 | self.headerPanes[count].part_content_set("left", btn) 129 | self.headerPanes[count].content_left_size = wdth 130 | 131 | nextHeader = Panes(self, size_hint_weight=EXPAND_HORIZ, 132 | size_hint_align=FILL_HORIZ) 133 | nextHeader.callback_unpress_add(self.paneResized) 134 | nextHeader.show() 135 | 136 | self.headerPanes[count].part_content_set("right", nextHeader) 137 | self.headerPanes.append(nextHeader) 138 | 139 | nextList.data["related"] = nextHeader 140 | nextHeader.data["related"] = nextList 141 | else: 142 | self.listPanes[count - 1].part_content_set("right", bx) 143 | self.headerPanes[count - 1].part_content_set("right", btn) 144 | 145 | self.lists.append(bx) 146 | 147 | def paneResized(self, obj): 148 | leftSize = obj.content_left_size 149 | rightSize = obj.content_right_size 150 | related = obj.data["related"] 151 | 152 | related.content_left_size = leftSize 153 | related.content_right_size = rightSize 154 | 155 | def row_pack(self, row, sort=True): 156 | 157 | """Takes a list of items and packs them to the table.""" 158 | 159 | assert len(row) == len(self.header_row), ( 160 | "The row you are trying to add to this sorted list has the wrong " 161 | "number of items! expected: %i got: %i" % ( 162 | len(self.header_row), len(row) 163 | ) 164 | ) 165 | 166 | self.rows.append(row) 167 | self.add_row(row) 168 | 169 | if sort: 170 | self.sort_by_column(self.sort_column) 171 | 172 | def add_row(self, row): 173 | #print("Test %s"%row) 174 | for count, item in enumerate(row): 175 | self.lists[count].pack_end(item) 176 | 177 | def row_unpack(self, row, delete=False): 178 | 179 | """Unpacks and hides, and optionally deletes, a row of items. 180 | The argument row can either be the row itself or its index number. 181 | """ 182 | if isinstance(row, int): 183 | row_index = row 184 | else: 185 | row_index = self.rows.index(row)+1 186 | 187 | # print("row index: " + str(row_index-1)) 188 | # print("length: " + str(len(self.rows))) 189 | # print("sort_data: " + str(row[self.sort_column].data["sort_data"])) 190 | 191 | row = self.rows.pop(row_index-1) 192 | 193 | for count, item in enumerate(row): 194 | self.lists[count].unpack(item) 195 | if delete: 196 | item.delete() 197 | else: 198 | item.hide() 199 | 200 | self.sort_by_column(self.sort_column, 201 | ascending=self.sort_column_ascending) 202 | 203 | def unpack_all(self): 204 | tmplist = list(self.rows) 205 | for rw in tmplist: 206 | self.row_unpack(rw) 207 | 208 | def reverse(self): 209 | rev_order = reversed(list(range(len(self.rows)))) 210 | for bx in self.lists: 211 | bx.unpack_all() 212 | 213 | for new_y in rev_order: 214 | self.add_row(self.rows[new_y]) 215 | 216 | lb = self.header_row[self.sort_column].part_content_get("icon") 217 | if lb is not None: 218 | if self.sort_column_ascending: 219 | lb.text = u"⬆" 220 | self.sort_column_ascending = False 221 | else: 222 | lb.text = u"⬇" 223 | self.sort_column_ascending = True 224 | 225 | self.rows.reverse() 226 | 227 | def sort_by_column(self, col, ascending=True): 228 | 229 | assert col >= 0 230 | assert col < len(self.header_row) 231 | 232 | self.header_row[self.sort_column].icon = None 233 | 234 | btn = self.header_row[col] 235 | ic = Label(btn) 236 | btn.part_content_set("icon", ic) 237 | ic.show() 238 | 239 | if ascending == True: #ascending: 240 | ic.text = u"⬇" 241 | self.sort_column_ascending = True 242 | else: 243 | ic.text = u"⬆" 244 | self.sort_column_ascending = False 245 | 246 | orig_col = [ 247 | (i, x[col].data.get("sort_data", x[col].text)) \ 248 | for i, x in enumerate(self.rows) 249 | ] 250 | sorted_col = sorted(orig_col, key=lambda e: e[1]) 251 | new_order = [x[0] for x in sorted_col] 252 | 253 | # print(new_order) 254 | 255 | if not ascending: 256 | new_order.reverse() 257 | 258 | # print(new_order) 259 | 260 | for bx in self.lists: 261 | bx.unpack_all() 262 | 263 | for new_y in new_order: 264 | self.add_row(self.rows[new_y]) 265 | 266 | self.rows.sort( 267 | key=lambda e: e[col].data.get("sort_data", e[col].text), 268 | #reverse=False if ascending else True 269 | ) 270 | self.sort_column = col 271 | 272 | def update(self): 273 | self.sort_by_column(self.sort_column, self.sort_column_ascending) 274 | -------------------------------------------------------------------------------- /elmextensions/tabbedbox.py: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | from efl.evas import EVAS_HINT_EXPAND, EVAS_HINT_FILL 4 | from efl.elementary.box import Box 5 | from efl.elementary.button import Button 6 | from efl.elementary.icon import Icon 7 | from efl.elementary.separator import Separator 8 | from efl.elementary.scroller import Scroller 9 | from efl.elementary.naviframe import Naviframe 10 | 11 | EXPAND_BOTH = EVAS_HINT_EXPAND, EVAS_HINT_EXPAND 12 | EXPAND_HORIZ = EVAS_HINT_EXPAND, 0.0 13 | FILL_BOTH = EVAS_HINT_FILL, EVAS_HINT_FILL 14 | FILL_HORIZ = EVAS_HINT_FILL, 0.5 15 | EXPAND_NONE = 0.0, 0.0 16 | ALIGN_CENTER = 0.5, 0.5 17 | ALIGN_RIGHT = 1.0, 0.5 18 | ALIGN_LEFT = 0.0, 0.5 19 | 20 | class TabbedBox(Box): 21 | def __init__(self, parent_widget, *args, **kwargs): 22 | Box.__init__(self, parent_widget, *args, **kwargs) 23 | 24 | self.tabs = [] 25 | self.currentTab = None 26 | self.tabChangedCallback = None 27 | self.closeCallback = None 28 | self.emptyCallback = None 29 | 30 | self.scr = Scroller(self, size_hint_weight=EXPAND_HORIZ, 31 | size_hint_align=FILL_BOTH) 32 | self.scr.content_min_limit(False, True) 33 | 34 | self.buttonBox = Box(self.scr, size_hint_weight=EXPAND_HORIZ, 35 | align=ALIGN_LEFT) 36 | self.buttonBox.horizontal = True 37 | self.buttonBox.show() 38 | 39 | self.scr.content = self.buttonBox 40 | self.scr.show() 41 | 42 | self.nf = Naviframe(self, size_hint_weight=EXPAND_BOTH, 43 | size_hint_align=FILL_BOTH) 44 | self.nf.show() 45 | 46 | self.pack_end(self.scr) 47 | self.pack_end(self.nf) 48 | 49 | def addTab(self, widget, tabName, canClose=True, disabled=False): 50 | self.tabs.append(widget) 51 | 52 | btn = Button(self.buttonBox, style="anchor", size_hint_align=ALIGN_LEFT) 53 | btn.text = tabName 54 | btn.data["widget"] = widget 55 | btn.disabled = disabled 56 | btn.callback_clicked_add(self.showTab, widget) 57 | btn.show() 58 | 59 | icn = Icon(self.buttonBox) 60 | icn.standard_set("gtk-close") 61 | icn.show() 62 | 63 | cls = Button(self.buttonBox, content=icn, style="anchor", size_hint_align=ALIGN_LEFT) 64 | cls.data["widget"] = widget 65 | cls.callback_clicked_add(self.closeTab) 66 | cls.disabled = disabled 67 | if canClose: 68 | cls.show() 69 | 70 | sep = Separator(self.buttonBox, size_hint_align=ALIGN_LEFT) 71 | sep.show() 72 | 73 | self.buttonBox.pack_end(btn) 74 | self.buttonBox.pack_end(cls) 75 | self.buttonBox.pack_end(sep) 76 | 77 | #Arguments go: btn, cls, sep 78 | widget.data["close"] = cls 79 | widget.data["button"] = btn 80 | widget.data["sep"] = sep 81 | 82 | self.showTab(widget=widget) 83 | 84 | def disableTab(self, tabIndex): 85 | btn, cls = self.tabs[tabIndex].data["button"], self.tabs[tabIndex].data["close"] 86 | btn.disabled = True 87 | cls.disabled = True 88 | 89 | def enableTab(self, tabIndex): 90 | btn, cls = self.tabs[tabIndex].data["button"], self.tabs[tabIndex].data["close"] 91 | btn.disabled = False 92 | cls.disabled = False 93 | 94 | def showTab(self, btn=None, widget=None): 95 | if type(btn) is int: 96 | widget = self.tabs[btn] 97 | if widget != self.currentTab: 98 | if self.currentTab: 99 | self.currentTab.data["button"].style="anchor" 100 | self.nf.item_simple_push(widget) 101 | self.currentTab = widget 102 | self.currentTab.data["button"].style="widget" 103 | 104 | if self.tabChangedCallback: 105 | self.tabChangedCallback(self, widget) 106 | 107 | def closeTab(self, btn): 108 | if not self.closeCallback: 109 | self.deleteTab(btn.data["widget"]) 110 | else: 111 | self.closeCallback(self, btn.data["widget"]) 112 | 113 | def deleteTab(self, widget): 114 | if type(widget) is int: 115 | widget = self.tabs[widget] 116 | 117 | del self.tabs[self.tabs.index(widget)] 118 | 119 | self.buttonBox.unpack(widget.data["close"]) 120 | self.buttonBox.unpack(widget.data["button"]) 121 | self.buttonBox.unpack(widget.data["sep"]) 122 | 123 | widget.data["close"].delete() 124 | widget.data["button"].delete() 125 | widget.data["sep"].delete() 126 | widget.delete() 127 | 128 | if self.currentTab == widget and len(self.tabs): 129 | self.showTab(widget=self.tabs[0]) 130 | 131 | if not len(self.tabs) and self.emptyCallback: 132 | self.emptyCallback(self) 133 | -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | This license applies to all files in this repository that do not have 2 | another license otherwise indicated. 3 | 4 | Copyright (c) 2014, Jeff Hoogland 5 | All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without 8 | modification, are permitted provided that the following conditions are met: 9 | * Redistributions of source code must retain the above copyright 10 | notice, this list of conditions and the following disclaimer. 11 | * Redistributions in binary form must reproduce the above copyright 12 | notice, this list of conditions and the following disclaimer in the 13 | documentation and/or other materials provided with the distribution. 14 | * Neither the name of the nor the 15 | names of its contributors may be used to endorse or promote products 16 | derived from this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 19 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY 22 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 24 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 25 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from distutils.core import setup 3 | from efl.utils.setup import build_extra, uninstall 4 | from elmextensions import __version__ 5 | 6 | 7 | setup( 8 | name = 'python-elm-extensions', 9 | version = __version__, 10 | description = 'A python module of useful elementary objects.', 11 | license="BSD-3-clause", 12 | author = 'Jeff Hoogland', 13 | author_email = 'JeffHoogland@Linux.com', 14 | url="https://github.com/JeffHoogland/python-elm-extensions", 15 | requires = ['efl (>= 1.19)'], 16 | provides = ['elmextensions'], 17 | packages = ['elmextensions'], 18 | cmdclass = { 19 | 'build': build_extra, 20 | 'uninstall': uninstall, 21 | }, 22 | command_options={ 23 | 'install': {'record': ('setup.py', 'installed_files.txt')} 24 | }, 25 | ) -------------------------------------------------------------------------------- /sortedlistother/sortedgenlist.py: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | from efl.elementary.genlist import Genlist, GenlistItem, GenlistItemClass, ELM_LIST_EXPAND 4 | from efl.elementary.label import Label 5 | from efl.elementary.box import Box 6 | from efl.elementary.button import Button 7 | from efl.elementary.separator import Separator 8 | from efl.elementary.scroller import Scroller, Scrollable, ELM_SCROLLER_POLICY_OFF, ELM_SCROLLER_POLICY_ON, ELM_SCROLLER_POLICY_AUTO 9 | from efl.evas import EVAS_HINT_EXPAND, EVAS_HINT_FILL 10 | 11 | EXPAND_BOTH = EVAS_HINT_EXPAND, EVAS_HINT_EXPAND 12 | EXPAND_HORIZ = EVAS_HINT_EXPAND, 0.0 13 | FILL_BOTH = EVAS_HINT_FILL, EVAS_HINT_FILL 14 | FILL_HORIZ = EVAS_HINT_FILL, 0.5 15 | 16 | class SortedList(Box): 17 | 18 | """ 19 | 20 | A "spread sheet like" widget for elementary. 21 | 22 | Argument "titles" is a list, with each element being a tuple: 23 | (, ) 24 | 25 | """ 26 | 27 | def __init__(self, parent_widget, titles=None, initial_sort=0, 28 | ascending=True, *args, **kwargs): 29 | Box.__init__(self, parent_widget, *args, **kwargs) 30 | 31 | self.header = titles 32 | self.sort_column = initial_sort 33 | self.sort_column_ascending = ascending 34 | 35 | self.rows = [] 36 | self.header_row = [] 37 | self.header_box = Box(self, size_hint_weight=EXPAND_HORIZ, 38 | size_hint_align=FILL_HORIZ) 39 | self.header_box.horizontal = True 40 | self.header_box.show() 41 | 42 | scr = Scroller(self, size_hint_weight=EXPAND_BOTH, 43 | size_hint_align=FILL_BOTH) 44 | 45 | self.list_box = Box(self, size_hint_weight=EXPAND_BOTH, 46 | size_hint_align=FILL_BOTH) 47 | self.list_box.horizontal = True 48 | self.list_box.show() 49 | 50 | scr.policy_set(ELM_SCROLLER_POLICY_OFF, ELM_SCROLLER_POLICY_ON) 51 | scr.content = self.list_box 52 | scr.show() 53 | 54 | self.lists = [] 55 | 56 | self.pack_end(self.header_box) 57 | self.pack_end(scr) 58 | self.show() 59 | 60 | if titles is not None: 61 | self.header_row_pack(titles) 62 | 63 | def header_row_pack(self, titles): 64 | 65 | """Takes a list (or a tuple) of tuples (string, bool) and packs them to 66 | the first row of the table.""" 67 | 68 | assert isinstance(titles, (list, tuple)) 69 | for t in titles: 70 | assert isinstance(t, tuple) 71 | assert len(t) == 2 72 | title, sortable = t 73 | try: 74 | assert isinstance(title, basestring) 75 | except: 76 | assert isinstance(title, str) 77 | assert isinstance(sortable, bool) 78 | 79 | def sort_btn_cb(button, col): 80 | if self.sort_column == col: 81 | self.reverse() 82 | else: 83 | self.sort_by_column(col) 84 | 85 | for count, t in enumerate(titles): 86 | title, sortable = t 87 | btn = Button(self, size_hint_weight=EXPAND_HORIZ, 88 | size_hint_align=FILL_HORIZ, text=title) 89 | btn.callback_clicked_add(sort_btn_cb, count) 90 | if not sortable: 91 | btn.disabled = True 92 | btn.show() 93 | self.header_box.pack_end(btn) 94 | self.header_row.append(btn) 95 | 96 | elm_list = ScrollableGenlist(self, size_hint_weight=EXPAND_BOTH, size_hint_align=FILL_BOTH) 97 | elm_list.policy_set(ELM_SCROLLER_POLICY_AUTO, ELM_SCROLLER_POLICY_OFF) 98 | elm_list.mode_set(ELM_LIST_EXPAND) 99 | #elm_list.go() 100 | elm_list.show() 101 | self.list_box.pack_end(elm_list) 102 | self.lists.append(elm_list) 103 | 104 | sep = Separator(self) 105 | sep.show() 106 | 107 | self.header_box.pack_end(sep) 108 | self.header_box.pack_end(sep) 109 | 110 | def row_pack(self, row, sort=True): 111 | 112 | """Takes a list of items and packs them to the table.""" 113 | 114 | assert len(row) == len(self.header_row), ( 115 | "The row you are trying to add to this sorted list has the wrong " 116 | "number of items! expected: %i got: %i" % ( 117 | len(self.header_row), len(row) 118 | ) 119 | ) 120 | 121 | self.rows.append(row) 122 | self.add_row(row) 123 | 124 | if sort: 125 | self.sort_by_column(self.sort_column) 126 | 127 | def add_row(self, row): 128 | def gl_text_get(obj, part, item_data): 129 | return item_data 130 | 131 | def gl_content_get(obj, part, data): 132 | pass 133 | 134 | def gl_state_get(obj, part, item_data): 135 | return False 136 | 137 | def gl_item_sel(gli, gl, *args, **kwargs): 138 | print("\n---GenlistItem selected---") 139 | print(gli) 140 | print(gl) 141 | print(args) 142 | print(kwargs) 143 | print(("item_data: %s" % gli.data_get())) 144 | 145 | itc = GenlistItemClass(item_style="default", 146 | text_get_func=gl_text_get, 147 | content_get_func=gl_content_get, 148 | state_get_func=gl_state_get) 149 | 150 | for count, item in enumerate(row): 151 | self.lists[count].item_append(itc, str(item), func=gl_item_sel) 152 | 153 | def row_unpack(self, row, delete=False): 154 | 155 | """Unpacks and hides, and optionally deletes, a row of items. 156 | 157 | The argument row can either be the row itself or its index number. 158 | 159 | """ 160 | if isinstance(row, int): 161 | row_index = row 162 | else: 163 | row_index = self.rows.index(row) - 1 164 | 165 | # print("row index: " + str(row_index-1)) 166 | # print("length: " + str(len(self.rows))) 167 | # print("sort_data: " + str(row[self.sort_column].data["sort_data"])) 168 | 169 | row = self.rows.pop(row_index) 170 | 171 | self.sort_by_column(self.sort_column, 172 | ascending=self.sort_column_ascending) 173 | 174 | def reverse(self): 175 | self.rows.reverse() 176 | for our_list in self.lists: 177 | our_list.clear() 178 | for row in self.rows: 179 | self.add_row(row) 180 | 181 | lb = self.header_row[self.sort_column].part_content_get("icon") 182 | if lb is not None: 183 | if self.sort_column_ascending: 184 | lb.text = u"⬆" 185 | self.sort_column_ascending = False 186 | else: 187 | lb.text = u"⬇" 188 | self.sort_column_ascending = True 189 | 190 | def sort_by_column(self, col, ascending=True): 191 | 192 | assert col >= 0 193 | assert col < len(self.header_row) 194 | 195 | self.header_row[self.sort_column].icon = None 196 | 197 | btn = self.header_row[col] 198 | ic = Label(btn) 199 | btn.part_content_set("icon", ic) 200 | ic.show() 201 | 202 | if ascending == True: #ascending: 203 | ic.text = u"⬇" 204 | self.sort_column_ascending = True 205 | else: 206 | ic.text = u"⬆" 207 | self.sort_column_ascending = False 208 | 209 | 210 | self.rows.sort( 211 | key=lambda e: e[col], 212 | #reverse=False if ascending else True 213 | ) 214 | 215 | if not ascending: 216 | self.rows.reverse() 217 | 218 | #Clear old data 219 | for our_list in self.lists: 220 | our_list.clear() 221 | 222 | for row in self.rows: 223 | self.add_row(row) 224 | 225 | self.sort_column = col 226 | 227 | def update(self): 228 | self.sort_by_column(self.sort_column, self.sort_column_ascending) 229 | 230 | class ScrollableGenlist(Genlist, Scrollable): 231 | def __init__(self, canvas, *args, **kwargs): 232 | Genlist.__init__(self, canvas, *args, **kwargs) 233 | -------------------------------------------------------------------------------- /sortedlistother/sortedlist.py: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | from efl.elementary.list import List, ELM_LIST_LIMIT, ELM_LIST_COMPRESS, ELM_LIST_EXPAND 4 | from efl.elementary.label import Label 5 | from efl.elementary.box import Box 6 | from efl.elementary.check import Check 7 | from efl.elementary.button import Button 8 | from efl.elementary.separator import Separator 9 | from efl.elementary.scroller import Scroller, Scrollable, ELM_SCROLLER_POLICY_OFF, ELM_SCROLLER_POLICY_ON, ELM_SCROLLER_POLICY_AUTO 10 | from efl.evas import EVAS_HINT_EXPAND, EVAS_HINT_FILL 11 | 12 | EXPAND_BOTH = EVAS_HINT_EXPAND, EVAS_HINT_EXPAND 13 | EXPAND_HORIZ = EVAS_HINT_EXPAND, 0.0 14 | FILL_BOTH = EVAS_HINT_FILL, EVAS_HINT_FILL 15 | FILL_HORIZ = EVAS_HINT_FILL, 0.5 16 | 17 | class SortedList(Box): 18 | 19 | """ 20 | 21 | A "spread sheet like" widget for elementary. 22 | 23 | Argument "titles" is a list, with each element being a tuple: 24 | (, ) 25 | 26 | """ 27 | 28 | def __init__(self, parent_widget, titles=None, initial_sort=None, 29 | ascending=True, *args, **kwargs): 30 | Box.__init__(self, parent_widget, *args, **kwargs) 31 | 32 | self.header = titles 33 | self.sort_column = initial_sort 34 | self.sort_column_ascending = ascending 35 | 36 | self.rows = [] 37 | self.header_row = [] 38 | self.header_box = Box(self, size_hint_weight=EXPAND_HORIZ, 39 | size_hint_align=FILL_HORIZ) 40 | self.header_box.horizontal = True 41 | self.header_box.show() 42 | 43 | scr = Scroller(self, size_hint_weight=EXPAND_BOTH, 44 | size_hint_align=FILL_BOTH) 45 | 46 | self.list_box = Box(self, size_hint_weight=EXPAND_BOTH, 47 | size_hint_align=FILL_BOTH) 48 | self.list_box.horizontal = True 49 | self.list_box.show() 50 | 51 | scr.policy_set(ELM_SCROLLER_POLICY_OFF, ELM_SCROLLER_POLICY_ON) 52 | scr.content = self.list_box 53 | scr.show() 54 | 55 | self.lists = [] 56 | 57 | self.pack_end(self.header_box) 58 | self.pack_end(scr) 59 | self.show() 60 | 61 | if titles is not None: 62 | self.header_row_pack(titles) 63 | 64 | def header_row_pack(self, titles): 65 | 66 | """Takes a list (or a tuple) of tuples (string, bool) and packs them to 67 | the first row of the table.""" 68 | 69 | assert isinstance(titles, (list, tuple)) 70 | for t in titles: 71 | assert isinstance(t, tuple) 72 | assert len(t) == 2 73 | title, sortable = t 74 | try: 75 | assert isinstance(title, basestring) 76 | except: 77 | assert isinstance(title, str) 78 | assert isinstance(sortable, bool) 79 | 80 | def sort_btn_cb(button, col): 81 | if self.sort_column == col: 82 | self.reverse() 83 | else: 84 | self.sort_by_column(col) 85 | 86 | for count, t in enumerate(titles): 87 | title, sortable = t 88 | btn = Button(self, size_hint_weight=EXPAND_HORIZ, 89 | size_hint_align=FILL_HORIZ, text=title) 90 | btn.callback_clicked_add(sort_btn_cb, count) 91 | if not sortable: 92 | btn.disabled = True 93 | btn.show() 94 | self.header_box.pack_end(btn) 95 | self.header_row.append(btn) 96 | 97 | elm_list = ScrollableList(self, size_hint_weight=EXPAND_BOTH, size_hint_align=FILL_BOTH) 98 | elm_list.policy_set(ELM_SCROLLER_POLICY_AUTO, ELM_SCROLLER_POLICY_OFF) 99 | elm_list.mode_set(ELM_LIST_EXPAND) 100 | elm_list.go() 101 | elm_list.show() 102 | self.list_box.pack_end(elm_list) 103 | self.lists.append(elm_list) 104 | 105 | sep = Separator(self) 106 | sep.show() 107 | 108 | self.header_box.pack_end(sep) 109 | self.header_box.pack_end(sep) 110 | 111 | def row_pack(self, row, sort=True): 112 | 113 | """Takes a list of items and packs them to the table.""" 114 | 115 | assert len(row) == len(self.header_row), ( 116 | "The row you are trying to add to this sorted list has the wrong " 117 | "number of items! expected: %i got: %i" % ( 118 | len(self.header_row), len(row) 119 | ) 120 | ) 121 | 122 | self.rows.append(row) 123 | self.add_row(row) 124 | 125 | if sort: 126 | self.sort_by_column(self.sort_column) 127 | 128 | def add_row(self, row): 129 | for count, item in enumerate(row): 130 | #check = Button(self) 131 | #check.show() 132 | self.lists[count].item_append(str(item)) 133 | 134 | def row_unpack(self, row, delete=False): 135 | 136 | """Unpacks and hides, and optionally deletes, a row of items. 137 | 138 | The argument row can either be the row itself or its index number. 139 | 140 | """ 141 | if isinstance(row, int): 142 | row_index = row 143 | else: 144 | row_index = self.rows.index(row) 145 | 146 | # print("row index: " + str(row_index-1)) 147 | # print("length: " + str(len(self.rows))) 148 | # print("sort_data: " + str(row[self.sort_column].data["sort_data"])) 149 | 150 | row = self.rows.pop(row_index) 151 | 152 | self.sort_by_column(self.sort_column, 153 | ascending=self.sort_column_ascending) 154 | 155 | def reverse(self): 156 | self.rows.reverse() 157 | for our_list in self.lists: 158 | our_list.clear() 159 | for row in self.rows: 160 | self.add_row(row) 161 | 162 | lb = self.header_row[self.sort_column].part_content_get("icon") 163 | if lb is not None: 164 | if self.sort_column_ascending: 165 | lb.text = u"⬆" 166 | self.sort_column_ascending = False 167 | else: 168 | lb.text = u"⬇" 169 | self.sort_column_ascending = True 170 | 171 | def sort_by_column(self, col, ascending=True): 172 | 173 | assert col >= 0 174 | assert col < len(self.header_row) 175 | 176 | if self.sort_column: 177 | self.header_row[self.sort_column].icon = None 178 | 179 | btn = self.header_row[col] 180 | ic = Label(btn) 181 | btn.part_content_set("icon", ic) 182 | ic.show() 183 | 184 | if ascending == True: #ascending: 185 | ic.text = u"⬇" 186 | self.sort_column_ascending = True 187 | else: 188 | ic.text = u"⬆" 189 | self.sort_column_ascending = False 190 | 191 | 192 | self.rows.sort( 193 | key=lambda e: e[col], 194 | #reverse=False if ascending else True 195 | ) 196 | 197 | if not ascending: 198 | self.rows.reverse() 199 | 200 | #Clear old data 201 | for our_list in self.lists: 202 | our_list.clear() 203 | 204 | for row in self.rows: 205 | self.add_row(row) 206 | 207 | self.sort_column = col 208 | 209 | def update(self): 210 | self.sort_by_column(self.sort_column, self.sort_column_ascending) 211 | 212 | class ScrollableList(List, Scrollable): 213 | def __init__(self, canvas, *args, **kwargs): 214 | List.__init__(self, canvas, *args, **kwargs) 215 | -------------------------------------------------------------------------------- /sortedlistother/test_sortedlist.py: -------------------------------------------------------------------------------- 1 | import random 2 | 3 | import efl.elementary as elm 4 | elm.init() 5 | from efl.elementary.window import StandardWindow 6 | from efl.elementary.label import Label 7 | from efl.elementary.button import Button 8 | from efl.evas import EVAS_HINT_EXPAND, EVAS_HINT_FILL 9 | 10 | from sortedlist import SortedList 11 | 12 | EXPAND_BOTH = EVAS_HINT_EXPAND, EVAS_HINT_EXPAND 13 | FILL_BOTH = EVAS_HINT_FILL, EVAS_HINT_FILL 14 | 15 | ROWS = 300 16 | COLUMNS = 5 17 | 18 | class derp(object): 19 | def __init__( self ): 20 | win = StandardWindow("Testing", "Elementary Sorted Table") 21 | win.callback_delete_request_add(lambda o: elm.exit()) 22 | 23 | titles = [] 24 | for i in range(COLUMNS): 25 | titles.append( 26 | ("Column " + str(i), True if i != 2 else False) 27 | ) 28 | 29 | slist = SortedList(win, titles=titles, size_hint_weight=EXPAND_BOTH, 30 | size_hint_align=FILL_BOTH) 31 | 32 | for i in range(ROWS): 33 | row = [] 34 | for j in range(COLUMNS): 35 | data = random.randint(0, ROWS*COLUMNS) 36 | row.append(data) 37 | slist.row_pack(row, sort=False) 38 | #slist.sort_by_column(1) 39 | slist.show() 40 | 41 | win.resize_object_add(slist) 42 | 43 | win.resize(600, 400) 44 | win.show() 45 | 46 | if __name__ == "__main__": 47 | GUI = derp() 48 | elm.run() 49 | elm.shutdown() 50 | -------------------------------------------------------------------------------- /test_aboutwindow.py: -------------------------------------------------------------------------------- 1 | import efl.elementary as elm 2 | from efl.elementary.window import StandardWindow 3 | from efl.elementary.label import Label 4 | elm.init() 5 | from efl.evas import EVAS_HINT_EXPAND, EVAS_HINT_FILL 6 | 7 | EXPAND_BOTH = EVAS_HINT_EXPAND, EVAS_HINT_EXPAND 8 | FILL_BOTH = EVAS_HINT_FILL, EVAS_HINT_FILL 9 | 10 | from elmextensions import AboutWindow 11 | 12 | AUTHORS = """ 13 |
14 | 15 | Jeff Hoogland (Jef91)
16 | Contact

17 | 18 | Wolfgang Morawetz (wfx)

19 | 20 | Kai Huuhko (kukko)

21 | 22 | """ 23 | 24 | LICENSE = """ 25 | 26 | 27 | GNU GENERAL PUBLIC LICENSE
28 | Version 3, 29 June 2007

29 |
30 | 31 | This program is free software: you can redistribute it and/or modify 32 | it under the terms of the GNU General Public License as published by 33 | the Free Software Foundation, either version 3 of the License, or 34 | (at your option) any later version.

35 | 36 | This program is distributed in the hope that it will be useful, 37 | but WITHOUT ANY WARRANTY; without even the implied warranty of 38 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 39 | GNU General Public License for more details.

40 | 41 | You should have received a copy of the GNU General Public License 42 | along with this program. If not, see
43 | http://www.gnu.org/licenses/ 44 | 45 | """ 46 | 47 | INFO = """ 48 | 49 | Elementary Python Extensions are awesome!
50 |
51 |
52 | 53 | """ 54 | 55 | class MainWindow(object): 56 | def __init__( self ): 57 | win = StandardWindow("Testing", "Elementary About Dialog") 58 | win.callback_delete_request_add(lambda o: elm.exit()) 59 | win.show() 60 | 61 | lbl = Label(win, size_hint_weight=EXPAND_BOTH, 62 | size_hint_align=FILL_BOTH) 63 | lbl.text = "This is a parent window for the About Dialog. Close when done." 64 | lbl.show() 65 | 66 | win.resize_object_add(lbl) 67 | 68 | win.resize(600, 400) 69 | win.show() 70 | 71 | AboutWindow(win, title="About Test", standardicon="dialog-information", \ 72 | version="1.0", authors=AUTHORS, \ 73 | licen=LICENSE, webaddress="https://github.com/JeffHoogland/python-elm-extensions", \ 74 | info=INFO) 75 | 76 | if __name__ == "__main__": 77 | GUI = MainWindow() 78 | elm.run() 79 | elm.shutdown() 80 | -------------------------------------------------------------------------------- /test_embeddedterminal.py: -------------------------------------------------------------------------------- 1 | import random 2 | 3 | import efl.elementary as elm 4 | elm.init() 5 | from efl.elementary.window import StandardWindow 6 | from efl.elementary.label import Label 7 | from efl.elementary.button import Button 8 | from efl.evas import EVAS_HINT_EXPAND, EVAS_HINT_FILL 9 | 10 | from elmextensions import EmbeddedTerminal 11 | 12 | EXPAND_BOTH = EVAS_HINT_EXPAND, EVAS_HINT_EXPAND 13 | FILL_BOTH = EVAS_HINT_FILL, EVAS_HINT_FILL 14 | 15 | class MainWindow(object): 16 | def __init__( self ): 17 | win = StandardWindow("Testing", "Elementary Embedded Terminal") 18 | win.callback_delete_request_add(lambda o: elm.exit()) 19 | 20 | term = EmbeddedTerminal(win, size_hint_weight=EXPAND_BOTH, size_hint_align=FILL_BOTH) 21 | 22 | term.show() 23 | 24 | win.resize_object_add(term) 25 | 26 | win.resize(600, 400) 27 | win.show() 28 | 29 | if __name__ == "__main__": 30 | GUI = MainWindow() 31 | elm.run() 32 | elm.shutdown() 33 | -------------------------------------------------------------------------------- /test_fileselector.py: -------------------------------------------------------------------------------- 1 | import random 2 | 3 | import efl.elementary as elm 4 | elm.init() 5 | from efl.elementary.window import StandardWindow 6 | from efl.elementary.label import Label 7 | from efl.elementary.button import Button 8 | from efl.evas import EVAS_HINT_EXPAND, EVAS_HINT_FILL 9 | 10 | from elmextensions import FileSelector 11 | 12 | EXPAND_BOTH = EVAS_HINT_EXPAND, EVAS_HINT_EXPAND 13 | FILL_BOTH = EVAS_HINT_FILL, EVAS_HINT_FILL 14 | 15 | class MainWindow(object): 16 | def __init__( self ): 17 | win = StandardWindow("Testing", "Elementary File Selector") 18 | win.callback_delete_request_add(lambda o: elm.exit()) 19 | 20 | self.fs = fs = FileSelector(win, size_hint_weight=EXPAND_BOTH, size_hint_align=FILL_BOTH) 21 | #fs.folderOnlySet(True) 22 | fs.setMode("Open") 23 | fs.show() 24 | 25 | fs.callback_activated_add(self.showFile) 26 | fs.callback_cancel_add(lambda o: elm.exit()) 27 | 28 | win.resize_object_add(fs) 29 | 30 | win.resize(600, 400) 31 | win.show() 32 | 33 | def showFile(self, fs, ourFile): 34 | print(ourFile) 35 | 36 | if __name__ == "__main__": 37 | GUI = MainWindow() 38 | elm.run() 39 | GUI.fs.shutdown() 40 | elm.shutdown() 41 | -------------------------------------------------------------------------------- /test_searchablelist.py: -------------------------------------------------------------------------------- 1 | import efl.elementary as elm 2 | from efl.elementary.window import StandardWindow 3 | elm.init() 4 | from efl.evas import EVAS_HINT_EXPAND, EVAS_HINT_FILL 5 | 6 | EXPAND_BOTH = EVAS_HINT_EXPAND, EVAS_HINT_EXPAND 7 | FILL_BOTH = EVAS_HINT_FILL, EVAS_HINT_FILL 8 | 9 | from elmextensions import SearchableList 10 | 11 | class MainWindow(object): 12 | def __init__( self ): 13 | win = StandardWindow("Testing", "Elementary SearchableList") 14 | win.callback_delete_request_add(lambda o: elm.exit()) 15 | 16 | ourList = SearchableList(win, size_hint_weight=EXPAND_BOTH, size_hint_align=FILL_BOTH) 17 | self.keys = ["Jeff", "Kristi", "Jacob", "Declan", "Joris", 18 | "Timmy", "Tristam"] 19 | for kbl in self.keys: 20 | ourList.item_append(kbl) 21 | ourList.show() 22 | 23 | win.resize_object_add(ourList) 24 | 25 | win.resize(600, 400) 26 | win.show() 27 | 28 | if __name__ == "__main__": 29 | GUI = MainWindow() 30 | elm.run() 31 | elm.shutdown() 32 | -------------------------------------------------------------------------------- /test_sortedlist.py: -------------------------------------------------------------------------------- 1 | #Used to generate some random numbers 2 | import random 3 | 4 | #Elementary stuff we want 5 | import efl.elementary as elm 6 | elm.init() 7 | from efl.elementary.window import StandardWindow 8 | from efl.elementary.label import Label 9 | from efl.elementary.button import Button 10 | from efl.elementary.separator import Separator 11 | from efl.elementary.box import Box 12 | from efl.evas import EVAS_HINT_EXPAND, EVAS_HINT_FILL 13 | 14 | #Our SortedList from elmextensions 15 | from elmextensions import SortedList 16 | 17 | #Fill commands 18 | EXPAND_BOTH = EVAS_HINT_EXPAND, EVAS_HINT_EXPAND 19 | FILL_BOTH = EVAS_HINT_FILL, EVAS_HINT_FILL 20 | 21 | #Defines how large our table generated will be 22 | ROWS = 50 23 | COLUMNS = 5 24 | 25 | class MainWindow(object): 26 | def __init__( self ): 27 | win = StandardWindow("Testing", "Elementary Sorted Table") 28 | win.callback_delete_request_add(lambda o: elm.exit()) 29 | 30 | """Build the titles for the table. The titles is a list of tuples with the following format: 31 | 32 | ( , )""" 33 | titles = [] 34 | for i in range(COLUMNS): 35 | titles.append( 36 | ("Column " + str(i), True if i != 2 else False) 37 | ) 38 | 39 | #Create our sorted list object 40 | slist = SortedList(win, titles=titles, size_hint_weight=EXPAND_BOTH, 41 | size_hint_align=FILL_BOTH) 42 | 43 | #Populate the rows in our table 44 | for i in range(ROWS): 45 | #Each row is a list with the number of elements that must equal the number of headers 46 | row = [] 47 | for j in range(COLUMNS): 48 | #Row elements can be ANY elementary object 49 | if j == 0: 50 | #For the first column in each row, we will create a button that will delete the row when pressed 51 | btn = Button(slist, size_hint_weight=EXPAND_BOTH, 52 | size_hint_align=FILL_BOTH) 53 | btn.text = "Delete row" 54 | btn.callback_clicked_add( 55 | lambda x, y=row: slist.row_unpack(y, delete=True) 56 | ) 57 | btn.show() 58 | #Add the btn created to our row 59 | row.append(btn) 60 | else: 61 | #For each other row create a label with a random number 62 | data = random.randint(0, ROWS*COLUMNS) 63 | lb = Label(slist, size_hint_weight=EXPAND_BOTH, 64 | size_hint_align=FILL_BOTH) 65 | lb.text=str(data) 66 | """For integer data we also need to assign value to "sort_data" because otherwise things get sorted as text""" 67 | lb.data["sort_data"] = data 68 | lb.show() 69 | #Append our label to the row 70 | row.append(lb) 71 | #Add the row into the SortedList 72 | slist.row_pack(row, sort=False) 73 | 74 | #Show the list 75 | slist.show() 76 | 77 | win.resize_object_add(slist) 78 | 79 | win.resize(600, 400) 80 | win.show() 81 | 82 | if __name__ == "__main__": 83 | GUI = MainWindow() 84 | elm.run() 85 | elm.shutdown() 86 | -------------------------------------------------------------------------------- /test_tabbedbox.py: -------------------------------------------------------------------------------- 1 | import efl.elementary as elm 2 | elm.init() 3 | from efl.elementary.window import StandardWindow 4 | from efl.elementary.label import Label 5 | from efl.evas import EVAS_HINT_EXPAND, EVAS_HINT_FILL 6 | 7 | from elmextensions import TabbedBox 8 | 9 | EXPAND_BOTH = EVAS_HINT_EXPAND, EVAS_HINT_EXPAND 10 | FILL_BOTH = EVAS_HINT_FILL, EVAS_HINT_FILL 11 | 12 | class MainWindow(object): 13 | def __init__( self ): 14 | win = StandardWindow("Testing", "Elementary Tabbed Widget") 15 | win.callback_delete_request_add(lambda o: elm.exit()) 16 | 17 | tabbs = TabbedBox(win, size_hint_weight=EXPAND_BOTH, size_hint_align=FILL_BOTH) 18 | tabbs.closeCallback = self.closeChecks 19 | 20 | for i in range(10): 21 | lbl = Label(win) 22 | lbl.text = "Tab %s"%i 23 | lbl.show() 24 | tabbs.addTab(lbl, "Tab %s"%i) 25 | 26 | tabbs.disableTab(0) 27 | tabbs.disableTab(3) 28 | 29 | tabbs.show() 30 | 31 | win.resize_object_add(tabbs) 32 | 33 | win.resize(600, 400) 34 | win.show() 35 | 36 | def closeChecks(self, tabbs, widget): 37 | print widget.text 38 | if widget.text != "Tab 1": 39 | tabbs.deleteTab(widget) 40 | 41 | if __name__ == "__main__": 42 | GUI = MainWindow() 43 | elm.run() 44 | elm.shutdown() 45 | --------------------------------------------------------------------------------