class EmcModule(object):
    name = ''
    label = ''
    icon = ''
    info = ''

    def __init__(self):
        pass

    def __shutdown__(self):
        pass

    def __restart__(self):
        self.__shutdown__()
        self.__init__() class EmcDialog(EmcLayout):
    """ TODO doc this
    style can be 'panel' or 'minimal'

    you can also apply special style that perform specific task:
       'info', 'error', 'warning', 'yesno', 'cancel', 'progress',
       'list', 'image_list_horiz', 'image_list_vert',
       'buffering'
    """

    minimal_styles = ['info', 'error', 'warning', 'yesno', 'cancel', 'progress']
    dialogs_counter = 0

    def __init__(self, title=None, text=None, content=None, spinner=False,
                 style='panel', done_cb=None, canc_cb=None, user_data=None):

        # load the right edje object
        if style in EmcDialog.minimal_styles or style == 'minimal':
            group = 'emc/dialog/minimal'
        elif style == 'buffering':
            group = 'emc/dialog/buffering'
        else:
            group = 'emc/dialog/panel'
        EmcLayout.__init__(self, layout, file=(theme_file, group),
                           focus_allow=True, is_focus_manager=True,
                           size_hint_align=FILL_BOTH,
                           size_hint_weight=EXPAND_BOTH)
        self.signal_callback_add('emc,dialog,close', '', self._close_pressed)
        self.signal_callback_add('emc,dialog,hide,done', '',
                                 (lambda a, s, d: self._delete_real()))
        self.signal_callback_add('emc,dialog,show,done', '',
                                 (lambda a, s, d: None))

        # put the dialog in the dialogs box of the main edje obj,
        # this way we only manage one edje and don't have stacking problems.
        # otherwise dialogs will stole the mouse events.
        box_append('dialogs.box.stack', self)

        EmcDialog.dialogs_counter += 1
        self._name = 'Dialog-' + str(EmcDialog.dialogs_counter)
        self._content = content
        self._done_cb = done_cb
        self._canc_cb = canc_cb
        self._user_data = user_data
        self._list = None
        self._textentry = None
        self._buttons = []

        # give focus to the dialog itself (for dialogs without any focusable)
        if style != 'buffering':
            self.focus = True

        # title
        if title is None:
            self.signal_emit('emc,dialog,title,hide', 'emc')
        else:
            self.part_text_set('emc.text.title', title)
            self.signal_emit('emc,dialog,title,show', 'emc')

        # vbox
        if style != 'buffering':
            self._vbox = elm.Box(self, horizontal=False, size_hint_align=FILL_HORIZ,
                                 size_hint_weight=EXPAND_HORIZ)
            self._vbox.show()
            self.content_set('emc.swallow.content', self._vbox)

        # if both text and content given then put them side by side
        if text and content:
            hbox = elm.Box(self, horizontal=True, size_hint_align=FILL_BOTH,
                           size_hint_weight=EXPAND_BOTH)
            hbox.show()
            self._vbox.pack_end(hbox)

        # text entry
        if text is not None:
            self._textentry = EmcScrolledEntry(parent=self, text=text,
                                               size_hint_weight=EXPAND_BOTH,
                                               size_hint_align=FILL_BOTH)
            self._textentry.show()

            if content:
                hbox.pack_end(self._textentry)
            else:
                self._vbox.pack_end(self._textentry)

        # user content
        if content is not None:
            frame = elm.Frame(self, style='pad_small', size_hint_align=FILL_BOTH,
                              size_hint_weight=EXPAND_BOTH, content=content)
            frame.show()
            if text is not None:
                hbox.pack_start(frame)
            else:
                self._vbox.pack_end(frame)

        # automatic list
        if style in ['list', 'image_list_horiz', 'image_list_vert']:
            self._list = EmcList(self, focus_allow=False,
                                 size_hint_align=FILL_BOTH, size_hint_weight=EXPAND_BOTH,
                                 horizontal=True if style == 'image_list_horiz' else False,
                                 style='dialog' if style == 'list' else 'image_list')
            self._list.callback_activated_add(self._list_item_activated_cb)
            self._list.show()
            self._vbox.pack_end(self._list)

        # spinner
        if spinner:
            self._spinner = elm.Progressbar(self, style='wheel', pulse_mode=True)
            self._spinner.pulse(True)
            self._spinner.show()
            self._vbox.pack_end(self._spinner)

        # set minimal styles + automatic title
        if style in EmcDialog.minimal_styles:
            self.signal_emit('emc,dialog,%s,set' % style, 'emc')
            if title is None:
                self.part_text_set('emc.text.title', style)
                self.signal_emit('emc,dialog,title,show', 'emc')

        # buttons
        if style in ('info', 'error', 'warning'):
            self.button_add(_('Ok'), lambda btn: self.delete())

        if style == 'yesno':
            if self._canc_cb:
                self.button_add(_('No'), lambda btn: self._canc_cb(self))
            else:
                self.button_add(_('No'), lambda btn: self.delete())

            if self._done_cb:
                self.button_add(_('Yes'), lambda btn: self._done_cb(self))
            else:
                self.button_add(_('Yes'), lambda btn: self.delete())

        if style == 'cancel':
            if canc_cb:
                self.button_add(_('Cancel'), lambda btn: self._canc_cb(self))
            else:
                self.button_add(_('Cancel'), lambda btn: self.delete())

        # listen for input events (not for the buffering dialog)
        if style != 'buffering':
            input_events.listener_add(self._name, self._input_event_cb)

        # show
        self.show()
        self.signal_emit('emc,dialog,show', 'emc')

    @staticmethod
    def activate():
        print('DEPRECATED EmcDialog.activate()')

    def delete(self):
        input_events.listener_del(self._name)
        self.signal_emit('emc,dialog,hide', 'emc')
        self.tree_focus_allow = False
        self.focus_allow = False
        for b in self._buttons:
            b.disabled = True

    def _delete_real(self):
        if self._textentry:
            self._textentry.delete()
        for b in self._buttons:
            b.delete()
        content = self.content_unset('emc.swallow.content')
        if content:
            content.delete()
        box_remove('dialogs.box.stack', self)
        elm.Layout.delete(self)
        del self

    def _close_pressed(self, a, s, d):
        if self._canc_cb:
            self._canc_cb(self)
        else:
            self.delete()

    def content_get(self):
        return self._content

    def data_get(self):
        return self._user_data

    def button_add(self, label, selected_cb=None, cb_data=None, icon=None, default=False):
        b = EmcButton(self, label, icon, selected_cb, cb_data)
        self.box_prepend('emc.box.buttons', b)
        self._buttons.append(b)
        if len(self._buttons) == 1:
            self.signal_emit('emc,dialog,buttons,show', 'emc')
            b.focus = True
        if default is True:
            b.focus = True
        return b

    def buttons_clear(self):
        for b in self._buttons:
            b.delete()
        del self._buttons
        self._buttons = []

    def title_set(self, text):
        if text is not None:
            self.part_text_set('emc.text.title', text)
            self.signal_emit('emc,dialog,title,show', 'emc')
        else:
            self.signal_emit('emc,dialog,title,hide', 'emc')

    def title_get(self):
        return self.part_text_get('emc.text.title')

    def text_set(self, text):
        if self._textentry:
            self._textentry.text_set(text)

    def text_get(self):
        return self._textentry.text_get()

    def text_append(self, text):
        self._textentry.text_set(self._textentry.text_get() + text)

    def list_item_append(self, label, icon=None, end=None, *args, **kwargs):
        if self._list:
            if isinstance(end, str) and end.startswith('text/'):
                end = elm.Label(self, style='dia_list', text=end[5:])
            it = self._list.item_append(label, load_icon(icon), load_icon(end), None)
            it.data['_user_item_data_'] = (args, kwargs)
            if not self._list.selected_item_get():
                it.selected = True
            return it

    def list_go(self):
        self._list.go()

    def list_clear(self):
        self._list.clear()

    def list_item_selected_get(self):
        if self._list:
            return self._list.selected_item_get()

    @staticmethod
    def list_item_icon_set(it, icon, end=False):
        it.part_content_set('end' if end else 'icon', load_icon(icon))

    def _list_item_activated_cb(self, li, it):
        if self._done_cb:
            args, kwargs = it.data['_user_item_data_']
            self._done_cb(self, *args, **kwargs)
        else:
            self.delete()

    def spinner_start(self):
        self._spinner.show()
        self._spinner.pulse(True)

    def spinner_stop(self):
        self._spinner.pulse(False)
        self._spinner.hide()

    def progress_set(self, val):
        self.edje.part_external_object_get('emc.dialog.progress').value_set(val)

    def autoscroll_enable(self, speed_scale=1.0, start_delay=3.0):
        self._textentry.autoscroll_start_delay = start_delay
        self._textentry.autoscroll_speed_scale = speed_scale
        self._textentry.autoscroll = True

    def focus_move_internal(self, direction):
        # try to move between list items
        if self._list or isinstance(self._content, (EmcList, EmcGenlist)):
            li = self._list or self._content
            if li.focus_move_internal(direction):
                return True

        # try to scroll the text entry
        if self._textentry:
            if direction == 'UP':
                self._textentry.scroll_by(0, -100)
            if direction == 'DOWN':
                self._textentry.scroll_by(0, +100)

        # try to move between dialog widgets
        self.focus_move(direction)

        # never let the focus goes out of the dialog
        return True

    def _input_event_cb(self, event):

        if not self.visible:
            return input_events.EVENT_CONTINUE

        if event in ['BACK', 'EXIT']:
            if self._canc_cb:
                self._canc_cb(self)
            else:
                self.delete()
            return input_events.EVENT_BLOCK

        if event == 'OK':
            if isinstance(self.focused_object, EmcButton):
                self.focused_object.activate()

            elif self._done_cb:
                if self._list:
                    it = self._list.selected_item
                    self._list_item_activated_cb(self._list, it)
                else:
                    self._done_cb(self)

            else:
                self.delete()

            return input_events.EVENT_BLOCK

        return input_events.EVENT_CONTINUE class EmcVKeyboard(EmcDialog):
    """ TODOC """

    def __init__(self, accept_cb=None, dismiss_cb=None,
                 title=None, text=None, user_data=None):
        """ TODOC """

        self.accept_cb = accept_cb
        self.dismiss_cb = dismiss_cb
        self.user_data = user_data
        self.buttons = list()

        # choose the correct layout
        self._available_layouts = ini.get_string_list('general', 'keyb_layouts')
        self._current_layout = self._available_layouts[0]  # TODO remember the last one used

        # table
        tb = elm.Table(layout, homogeneous=True)
        tb.show()
        self._table = tb

        # init the parent EmcDialog class
        EmcDialog.__init__(self, title=title, style='minimal', content=tb)

        # set dialog title
        self.part_text_set('emc.text.title', title or _('Insert text'))

        # entry
        self.entry = EmcTextEntry(self, style='vkeyboard', scrollable=True,
                                  single_line=True, editable=True,
                                  context_menu_disabled=True, focus_allow=True,
                                  size_hint_weight=EXPAND_BOTH, size_hint_align=FILL_BOTH)
        self.entry.callback_activated_add(self._accept_cb)
        self.entry.callback_aborted_add(self._dismiss_cb)
        if text: self.text_set(text)
        tb.pack(self.entry, 0, 0, 12, 1)
        self.entry.show()

        # numbers + backspace
        for i, c in enumerate('1234567890'):
            self._pack_btn(i, 1, 1, 1, c, cb=self._default_btn_cb)
        self._pack_btn(10, 1, 2, 1, _('ERASE'), cb=self._erase_cb)

        # characters
        self._apply_layout(self._current_layout)

        # last 2 rows
        self._pack_btn(0, 5, 4, 1, keyboard_layouts[self._current_layout][0],
                       cb=self._change_layout_cb)
        self._pack_btn(4, 5, 4, 1, _('SPACE'), cb=self._space_cb)
        self._pack_btn(8, 5, 4, 1, _('UPPERCASE'), cb=self._uppercase_cb)

        self._pack_btn(0, 6, 5, 1, _('Dismiss'), 'icon/cancel', self._dismiss_cb)
        self._pack_btn(5, 6, 1, 1, None, 'icon/arrowL',
                       lambda b: self.entry.cursor_prev())
        self._pack_btn(6, 6, 1, 1, None, 'icon/arrowR',
                       lambda b: self.entry.cursor_next())
        self._pack_btn(7, 6, 5, 1, _('Accept'), 'icon/ok', self._accept_cb)

        # catch input events
        input_events.listener_add('vkbd', self.input_event_cb)
        self.entry.focus = True

    def _pack_btn(self, x, y, w, h, label, icon=None, cb=None):
        b = EmcButton(self, label, icon, cb=cb, size_hint_align=FILL_HORIZ)
        self._table.pack(b, x, y, w, h)
        self.buttons.append(b)
        return b

    def delete(self):
        input_events.listener_del('vkbd')
        EmcDialog.delete(self)

    def text_set(self, text):
        self.entry.text = text
        self.entry.cursor_end_set()

    def _dismiss_cb(self, button):
        if self.dismiss_cb and callable(self.dismiss_cb):
            self.dismiss_cb(self)
        self.delete()

    def _accept_cb(self, button):
        if self.accept_cb and callable(self.accept_cb):
            if self.user_data is not None:
                ret = self.accept_cb(self, self.entry.entry_get(), self.user_data)
            else:
                ret = self.accept_cb(self, self.entry.entry_get())
            if ret is False:
                return
        self.delete()

    def _default_btn_cb(self, button):
        self.entry.entry_insert(button.text)

    def _erase_cb(self, button):
        pos = self.entry.cursor_pos_get()
        if pos > 0:
            text = self.entry.text
            self.entry.text = text[:pos - 1] + text[pos:]
            self.entry.cursor_pos_set(pos - 1)

    def _space_cb(self, button):
        self.entry.entry_insert(' ')

    def _uppercase_cb(self, button):
        if self._current_layout == 'symbols':
            return

        for btn in self.buttons:
            c = btn.text_get()
            if c and len(c) == 1 and c.isalpha():
                if c.islower():
                    btn.text = c.upper()
                    button.text = _('LOWERCASE')
                else:
                    btn.text = c.lower()
                    button.text = _('UPPERCASE')
            elif c and len(c) == 1:
                if c == '(':
                    btn.text = '['
                elif c == ')':
                    btn.text = ']'
                elif c == '[':
                    btn.text = '('
                elif c == ']':
                    btn.text = ')'
                elif c == '.':
                    btn.text = ':'
                elif c == ':':
                    btn.text = '.'
                elif c == ',':
                    btn.text = ';'
                elif c == ';':
                    btn.text = ','
                elif c == '-':
                    btn.text = '_'
                elif c == '_':
                    btn.text = '-'
                elif c == '?':
                    btn.text = '!'
                elif c == '!':
                    btn.text = '?'
                elif c == '+':
                    btn.text = '*'
                elif c == '*':
                    btn.text = '+'
                elif c == '@':
                    btn.text = '/'
                elif c == '/':
                    btn.text = '@'

    def _apply_layout(self, layout):
        for j in range(2, 5):
            for i in range(12):
                old = self._table.child_get(i, j)
                if old:
                    old.delete()

        self._current_layout = layout
        for j in range(3):
            for i, c in enumerate(keyboard_layouts[self._current_layout][j + 1]):
                if c != ' ':
                    self._pack_btn(i, j + 2, 1, 1, c, cb=self._default_btn_cb)

    def _change_layout_cb(self, button):
        new_idx = self._available_layouts.index(self._current_layout) + 1
        if new_idx >= len(self._available_layouts):
            new_idx = 0
        self._apply_layout(self._available_layouts[new_idx])
        button.text = keyboard_layouts[self._current_layout][0]

    def input_event_cb(self, event):
        if event == 'EXIT':
            self._dismiss_cb(None)
            return input_events.EVENT_BLOCK

        return input_events.EVENT_CONTINUE class EmcFolderSelector(EmcDialog):
    """
    Open a dialog that allow the user to choose a path on the filesystem.

    Args:
       title:
          The (optional) dialog title.
       done_cb:
          The (mandatory) function to call when the selection is done.
          Signature: func(path, **kargs)
       **kargs:
          Any other keyword arguments will be passed back in the done_cd func
    """

    def __init__(self, title=_('Source Selector'), done_cb=None, **kargs):
        self._user_cb = done_cb
        self._user_kargs = kargs

        EmcDialog.__init__(self, title, style='list', done_cb=self._btn_browse_cb)
        self.button_add(_('Select'), self._btn_select_cb)
        self.button_add(_('Browse'), self._btn_browse_cb, default=True)

        self.populate_devices()

    def populate_devices(self):
        self.list_clear()

        # other storage devices
        for dev in storage.list_devices():
            if dev.is_mounted:
                it = self.list_item_append(dev.label, dev.icon)
                it.data['root'] = it.data['path'] = dev.mount_point

        self.list_go()

    def populate_folder(self, root, folder):
        if folder == '':  # back in '/'
            self.populate_devices()
            return

        try:
            folders = os.listdir(folder)
        except PermissionError:
            EmcDialog(style='error', text=_('Permission denied'))
            return

        self.list_clear()

        # back item
        parent = os.path.normpath(os.path.join(folder, '..'))
        it = self.list_item_append(_('Back'), 'icon/back')
        it.data['root'] = root
        it.data['path'] = parent if parent != folder else ''  # back in '/'

        # folders
        for fname in utils.natural_sort(folders):
            fullpath = os.path.join(folder, fname)
            if fname[0] != '.' and os.path.isdir(fullpath):
                it = self.list_item_append(fname, 'icon/folder')
                it.data['root'] = root
                it.data['path'] = fullpath

        self.list_go()

    def _btn_browse_cb(self, btn):
        it = self.list_item_selected_get()
        if len(it.data['path']) < len(it.data['root']):
            self.populate_devices()
        else:
            self.populate_folder(it.data['root'], it.data['path'])

    def _btn_select_cb(self, btn):
        path = self.list_item_selected_get().data['path']
        if path and callable(self._user_cb):
            self._user_cb('file://' + path, **self._user_kargs)
        self.delete() class EmcButton(_EmcFocusable, elm.Button):
    """ A simple wrapper around the elm Button class """

    def __init__(self, parent, label=None, icon=None, cb=None, cb_data=None,
                 toggle=False, focus_allow=True, **kargs):

        elm.Button.__init__(self, parent, style='emc', **kargs)
        _EmcFocusable.__init__(self, parent, focus_allow=focus_allow)

        self._cb = cb
        self._cb_data = cb_data
        self._is_toggle = toggle
        self._toggled = False

        self.callback_clicked_add(self.activate)
        if label: self.text_set(label)
        if icon: self.icon_set(icon)
        self.show()

    def icon_set(self, icon):
        self.content_set(load_icon(icon))

    @property
    def toggled(self):
        return self._toggled

    @toggled.setter
    def toggled(self, toggled):
        self._toggled = toggled
        if toggled:
            self.signal_emit('emc,state,toggled', 'emc')
        else:
            self.signal_emit('emc,state,untoggled', 'emc')

    def activate(self, obj=None):
        self.focus = True

        if self._is_toggle:
            self.toggled = not self._toggled
        elif obj is None:
            self.signal_emit("elm,anim,activate", "elm")

        if callable(self._cb):
            if self._cb_data is not None:
                self._cb(self, self._cb_data)
            else:
                self._cb(self) class EmcNotify(edje.Edje):
    """ TODO doc this"""

    def __init__(self, text, icon='icon/star', hidein=5.0, close_cb=None):
        self.timer = None
        self.close_cb = close_cb

        edje.Edje.__init__(self, layout.evas, file=theme_file,
                           group='emc/notify/default')
        self.part_text_set('emc.text.caption', text)
        self._icon = load_image(icon)
        self.part_swallow('emc.swallow.icon', self._icon)
        box_append('notify.box.stack', self)

        if hidein > 0.0:
            self.hidein(hidein)

        self.show()

    def hidein(self, hidein):
        if self.timer:
            self.timer.delete()
        self.timer = ecore.Timer(hidein, self._hide_timer_cb)

    def _hide_timer_cb(self):
        box_remove('notify.box.stack', self)
        self._icon.delete()
        self.delete()
        if callable(self.close_cb):
            self.close_cb()
        return ecore.ECORE_CALLBACK_CANCEL

    def close(self):
        if self.timer:
            self.timer.delete()
        self._hide_timer_cb()

    def text_set(self, text):
        self.part_text_set('emc.text.caption', text) class EmcMenu(_EmcFocusable, elm.Ctxpopup):
    """ Dont forget to call show() AFTER all items added """

    def __init__(self, relto=None, dismiss_on_select=True):
        self.dismiss_on_select = dismiss_on_select

        # base popup class
        elm.Ctxpopup.__init__(self, layout, style='emc_menu', direction_priority=(
            elm.ELM_CTXPOPUP_DIRECTION_DOWN, elm.ELM_CTXPOPUP_DIRECTION_UP,
            elm.ELM_CTXPOPUP_DIRECTION_RIGHT, elm.ELM_CTXPOPUP_DIRECTION_LEFT)
                              )
        _EmcFocusable.__init__(self, layout, is_focus_manager=True,
                               focus_allow=False)
        self.callback_dismissed_add(self._dismissed_cb)

        # Scrollable list as content
        self.li = EmcList(self, style='emc_menu', name='MenuList',
                          mode=elm.ELM_LIST_EXPAND)
        self.li.content_min_limit(True, False)
        self.li.callback_activated_add(self._item_activated_cb)
        self.li.on_mouse_move_add(self._mouse_move_cb)
        self.li.on_mouse_down_add(self._mouse_click_cb)
        self.content = self.li

        # adjust popup position
        if relto:
            x, y = relto.center
            self.move(x, y)

        input_events.listener_add('EmcMenu', self._input_event_cb)

    def show(self):
        # adjust list size
        win_w, win_h = win.size
        self.li.size_hint_max = (-1, win_h / 1.5)
        self.li.go()

        # show the popup
        elm.Ctxpopup.show(self)

        # auto select first (not disabled or separator) item
        if self.li.selected_item is None:
            item = self.li.first_item
            while item and item.disabled:
                item = item.next
            if item:
                item.selected = True

        # give focus to the inner list
        self.li.focus = True

    def close(self):
        self.li.focus_allow = False
        self.dismiss()

    def item_add(self, label=None, icon=None, end=None, callback=None, *args, **kargs):
        item = self.li.item_append(label, load_icon(icon), load_icon(end))
        item.data['_user_cb_data_'] = (callback, args, kargs)
        return item

    def item_separator_add(self):
        item = self.li.item_append()
        item.separator = True
        item.disabled = True
        return item

    @staticmethod
    def item_icon_set(item, icon):
        item.content_set(load_icon(icon))

    @staticmethod
    def item_icon_end_set(item, icon):
        item.part_content_set('end', load_icon(icon))

    def _dismissed_cb(self, obj):
        input_events.listener_del('EmcMenu')
        self.delete()
        del self

    def _item_activated_cb(self, obj, item):
        cb, args, kwargs = item.data['_user_cb_data_']
        if callable(cb):
            cb(self, item, *args, **kwargs)
        if self.dismiss_on_select:
            self.close()

    def _mouse_move_cb(self, obj, event):
        item, pos = self.li.at_xy_item_get(*event.position.canvas)
        if item and not item.disabled:
            item.selected = True

    def _mouse_click_cb(self, obj, event):
        self._item_activated_cb(self.li, self.li.selected_item)

    def focus_move_internal(self, direction):
        if direction in ('LEFT', 'RIGHT'):
            return True
        d = self.direction
        if (d == elm.ELM_CTXPOPUP_DIRECTION_DOWN and direction == 'UP') or \
                (d == elm.ELM_CTXPOPUP_DIRECTION_UP and direction == 'DOWN'):
            self.close()
        return True

    def _input_event_cb(self, event):
        if event == 'OK':
            self._item_activated_cb(self.li, self.li.selected_item)
            return input_events.EVENT_BLOCK
        elif event in ('BACK', 'EXIT'):
            self.close()
            return input_events.EVENT_BLOCK
        return input_events.EVENT_CONTINUE class DownloadManager(utils.Singleton):
    """ Manage a queue of urls to download.

    The only function you need is queue_download, the class is a singleton so
    you must use as:
    DownloadManager().queue_download(url, ...)

    """

    def __init__(self):
        if not hasattr(self, 'queue'):
            self.queue = []  # list of DManItem instances

    def queue_download(self, url, dest_name=None, dest_ext=None, dest_folder=None):
        """ Put a new url in the download queue.

        Asking the user to confirm the operation, and giving the ability to
        change destination folder and destination name.

        Args:
           url: a valid url to download.
           dest_name: suggested name for the file, without extension.
           dest_ext: extension for the file, if None it is extracted from the url.
           dest_folder: where to put the downloaded file.

        """
        # destination folder
        if dest_folder is None:
            dest_folder = ini.get('general', 'download_folder')

        # destination file extension
        if dest_ext is None:
            try:
                # try to get the file extension from the url
                if '?' in url:
                    dest_ext = url.split('?')[0].split('.')[-1]
                else:
                    dest_ext = url.split('.')[-1]
                assert 6 > len(dest_ext) > 1
            except:
                dest_ext = 'mp4'  # :/

        # destination file name
        fname = '.'.join((dest_name or 'epymc_download', dest_ext))

        # confirm dialog
        dia = EmcDialog(_('Download Manager'), text='')
        dia.data['url'] = url
        dia.data['dst_folder'] = dest_folder
        dia.data['dst_name'] = fname
        dia.button_add(_('Start download'), self._start_cb, dia)
        dia.button_add(_('Rename'), self._change_name_cb, dia)
        dia.button_add(_('Change folder'), self._change_folder_cb, dia)
        self._update_dialog_text(dia)

    # confirmation dialog stuff
    @staticmethod
    def _update_dialog_text(dia):
        text = _('File will be downloaded in the folder:<br> <b>{}</b><br>'
                 '<br>File will be named as:<br> <b>{}</b><br>'
                 '<br>You can rename the file using the <i>Rename</i> button or '
                 'change the destination folder from the main configuration.') \
            .format(dia.data['dst_folder'], dia.data['dst_name'])
        dia.text_set(text)

    def _change_name_cb(self, bt, dia):
        EmcVKeyboard(title=_('Rename download'), text=dia.data['dst_name'],
                     accept_cb=self._change_name_done_cb, user_data=dia)

    def _change_name_done_cb(self, vkeyb, text, dia):
        dia.data['dst_name'] = text
        self._update_dialog_text(dia)

    def _change_folder_cb(self, bt, dia):
        EmcFolderSelector(title=_('Change destination folder'),
                          done_cb=self._change_folder_done_cb, dia=dia)

    def _change_folder_done_cb(self, folder, dia):
        dia.data['dst_folder'] = folder.replace('file://', '')
        self._update_dialog_text(dia)

    def _start_cb(self, bt, dia):
        url = dia.data['url']
        fullpath = os.path.join(dia.data['dst_folder'], dia.data['dst_name'])
        fullpath = utils.ensure_file_not_exists(fullpath)

        # check if the given url is already in queue
        if len([i for i in self.queue if i.url == url]) > 0:
            text = _('The file %s is in the download queue yet') % dia.data['dst_name']
            EmcDialog(style='error', text=text)
            dia.delete()
            return

        # close the dialog
        dia.delete()

        # queue the download
        self.queue.append(DManItem(self, url, fullpath))
        self._process_queue()

    # process queue
    def _process_queue(self):
        # no items in queue, nothing to do
        if len(self.queue) < 1:
            return

        # respect the max_concurrent_download option
        slots = ini.get_int('general', 'max_concurrent_download')
        for item in self.queue:
            if item.status == DMAN_RUNNING:
                slots -= 1
                if slots <= 0:
                    return

        # start queued items (always respecting max_concurrent_download)
        for item in self.queue:
            if item.status == DMAN_QUEUED:
                item.start()
                slots -= 1
                if slots <= 0:
                    return

    # in progress dialog stuff
    def in_progress_show(self):
        """ Show a dialog with the operation in progress """

        if len(self.queue) < 1:
            EmcDialog(title=_('Download manager'), style='info',
                      text='No download currently in progress')
            return

        itc = elm.GenlistItemClass(item_style='default',
                                   text_get_func=self._gl_text_get)
        gl = elm.Genlist(layout, style='dman', focus_allow=False,
                         homogeneous=True, mode=elm.ELM_LIST_COMPRESS)

        self.dia = EmcDialog(title=_('Download manager'), content=gl,
                             canc_cb=self.in_progress_hide)
        self.dia.button_add(_('Close'), selected_cb=self.in_progress_hide)
        self.dia.button_add(_('Start'), selected_cb=self._dia_start_cb)
        self.dia.button_add(_('Clear completed'), selected_cb=self._dia_clear_cb)
        self.dia.button_add(_('Cancel'), selected_cb=self._dia_cancel_cb)

        for item in self.queue:
            gl.item_append(itc, item)
        gl.first_item.selected = True

        # start a timer to continuosly update the list
        self.dia_timer = ecore.Timer(1.0, self._dia_update_timer_cb, gl)

    def in_progress_hide(self, *args):
        """ dismiss the progress dialog """
        if self.dia_timer:
            self.dia_timer.delete()
            self.dia_timer = None
        if self.dia:
            self.dia.delete()
            self.dia = None

    @staticmethod
    def _gl_text_get(obj, part, item):
        if part == 'elm.text.fname':
            return item.fname
        if part == 'elm.text.folder':
            return item.folder
        if part == 'elm.text.progress':
            if item.total_size > 0:
                percent = int((item.done_size / item.total_size) * 100)
                return _('{0}% of {1}').format(percent, utils.hum_size(item.total_size))
            else:
                return _('Unknown size')
        if part == 'elm.text.status':
            if item.status == DMAN_QUEUED:
                return _('queued')
            elif item.status == DMAN_RUNNING:
                return _('running')
            elif item.status == DMAN_COMPLETED:
                return _('completed')
            elif item.status == DMAN_PAUSED:
                return _('paused')

    @staticmethod
    def _dia_update_timer_cb(gl):
        gl.realized_items_update()
        return ecore.ECORE_CALLBACK_RENEW

    def _dia_start_cb(self, btn):
        gl_item = self.dia.content_get().selected_item
        if gl_item is not None:
            gl_item.data.start()

    def _dia_cancel_cb(self, btn):
        gl_item = self.dia.content_get().selected_item
        if gl_item is None:
            return

        item = gl_item.data
        txt = '<b>{}</b><br>'.format(item.fname)
        txt += _('Are you sure you want to abort the download of the file?')
        EmcDialog(style='yesno', title=_('Delete download'), text=txt,
                  done_cb=self._dia_cancel_confirmed_cb, user_data=gl_item)

    def _dia_cancel_confirmed_cb(self, dia):
        gl_item = dia.data_get()
        item = gl_item.data
        dia.delete()

        item.abort()
        self.queue.remove(item)
        del item

        to_sel = gl_item.next or gl_item.prev
        if to_sel:
            to_sel.selected = True
        gl_item.delete()

        self._process_queue()

    def _dia_clear_cb(self, btn):
        gl = self.dia.content_get()
        for gl_item in gl:
            item = gl_item.data
            if item.status == DMAN_COMPLETED:
                gl_item.delete()
                self.queue.remove(item)
                del item
        if gl.selected_item is None and gl.first_item:
            gl.first_item.selected = True class EmcSlider(_EmcFocusable, elm.Slider):
    """ Simple wrapper around the elm Slider class """

    def __init__(self, parent, focus_allow=True, **kargs):
        elm.Slider.__init__(self, parent, style='emc', **kargs)
        _EmcFocusable.__init__(self, parent, focus_allow=focus_allow,
                               is_focus_manager=True)
        self.show()

    def focus_move_internal(self, direction):
        if direction == 'RIGHT':  # TODO support vertical
            self.value += self.step
            self.callback_call('changed')
            return True

        elif direction == 'LEFT':
            self.value -= self.step
            self.callback_call('changed')
            return True

        return False class EmcBrowser(object):
    """
    This is the browser object, it is used to show various page each containing
    a list. Usually you need a single instance of this class for all your needs.
    In order you have to:
     1. implement at least one class that inherit from EmcItemClass
     2. create an instance of EmcBrowser
     3. add a page to the browser using the page_add() method
     4. add items to the current page using item_add(MyItemClass(), url, user_data)
    Later you can create new pages or use back(), clear(), show(), hide()
    """

    def __init__(self, name, default_style='List', icon='icon/home'):
        DBG('EmcBrowser __init__')
        _instances.append(self)
        self.name = name
        self.default_style = default_style
        self.icon = icon

        self.pages = []
        self.current_view = None
        self.autoselect_url = None
        self._freezed = False

    def __str__(self):
        text = '=' * 70 + '\n'
        text += '===  %s  %s\n' % (self.name, '=' * (63 - len(self.name)))
        text += '== name: %s  pages: %d curview: %s\n' % \
                (self.name, len(self.pages), self.current_view)
        for p in self.pages:
            text += '== page: ' + str(p) + '\n'
        text += '=' * 70 + '\n'
        # for s in _style_memory:
        #    text += 'style mem: %s  style: %s\n' % (s, _style_memory[s])
        # text += '*' * 70 + '\n'
        return text

    def delete(self):
        _instances.remove(self)
        del self

    def _search_style_in_parent(self):
        for p in reversed(self.pages):
            if _memorydb.id_exists(p['url']):
                return _memorydb.get_data(p['url'])
        return None

    def page_add(self, url, title, styles, populate_cb, *args, **kwargs):
        """
        When you create a page you need to give at least the url, the title
        and the populate callback. Every other arguments will be passed back
        in the callback. style can be None to use the default page style,
        usually the plain list.

        Args:
           url: A unique string id for the page
           title: Readable text for the user
           styles: A tuple with all the style that the page can show.
                   Available styles: 'List', 'PosterGrid', 'CoverGrid'
                   If set to None it default to the default style given at
                   the Browser instance creation.
                   The first item is the default one.
           populate_cb: Function to call when the page need to be populated.
                        Signature: func(browser, url, *args, **kwargs)
        """

        # choose the style of the new page
        if styles is None:
            styles = (self.default_style,)
        if _memorydb.id_exists(url):
            style = _memorydb.get_data(url)
        else:
            style = self._search_style_in_parent()
        if not style:
            style = styles[0]
        if ini.get_bool('general', 'ignore_views_restrictions') is False:
            if style not in styles:
                style = styles[0]

        # get the correct view instance
        view = self._create_or_get_view(style)

        # append the new page in the pages list
        page = {'view': view, 'url': url, 'title': title, 'styles': styles,
                'cb': populate_cb, 'args': args, 'kwargs': kwargs}
        self.pages.append(page)

        # first time, we don't have a current_view, set it
        if not self.current_view:
            self.current_view = view

        # switch to the new page
        self._populate_page(page)

    def item_add(self, item_class, url, user_data=None):
        """
        Use this method to add an item in the current (last added) page
        Url should be (but its not mandatory) a full correct url in the form:
        file:///home/user/some/dir, it MUST be unique in all the browser
        and MUST not contain any strange chars.
        """
        self.current_view.item_add(item_class, url, user_data,
                                   True if (url and url == self.autoselect_url) else False)

    def group_add(self, label, icon=None):
        """ TODO DOC """
        self.current_view.group_add(label, icon)

    def back(self):
        """ TODO Function doc """
        # discard current page
        old_page = self.pages.pop()
        self.autoselect_url = old_page['url']
        del old_page

        # no more page to go back, hide the view and return to main menu
        if len(self.pages) == 0:
            self.hide()
            mainmenu.show()
            return

        # switch to the previous page
        page_data = self.pages[-1]
        self._populate_page(page_data, is_back=True)

    def refresh(self, hard=False):
        if self.pages and self.current_view:
            if hard:
                # try to keep the selected item selected
                self.autoselect_url = self.current_view.selected_url_get()
                # re-create the page
                self._populate_page(self.pages[-1], is_refresh=True)
            else:
                self.current_view.refresh()

    def change_style(self, style):
        # the current page is always the last one
        page = self.pages[-1]

        # check if the style is valid (unless not explicitly ignored)
        if ini.get_bool('general', 'ignore_views_restrictions') is False:
            if style not in page['styles']:
                DBG('Style %s not available for this page' % style)
                EmcDialog(style='info', title=_('View restriction'),
                          text=_('<br><br>The requested view is not enabled for the current '
                                 'page.<br><br><small><info>NOTE: You can ovveride this '
                                 'restriction in the <i>Configuration → Views</i> '
                                 'section.</info></small>'))
                return

        # change only if needed
        view = self._create_or_get_view(style)
        if view == self.current_view:
            return

        # remember the selected item
        self.autoselect_url = self.current_view.selected_url_get()

        # clear & hide the current view
        self.current_view.clear()
        self.current_view.hide()

        # set the new view in the current (always the last) page
        page['view'] = view

        # remember the user choice
        global _memorydb
        page_url = self.pages[-1]['url']
        _memorydb.set_data(page_url, style)

        # recreate the page
        self.refresh(hard=True)

    def clear(self):
        """ TODO Function doc """
        self.current_view = None
        self.autoselect_url = None
        self._freezed = False
        self.pages = []

    def show(self):
        """ TODO Function doc """
        gui.signal_emit('topbar,show')
        gui.swallow_set('topbar.icon', EmcImage(self.icon))
        self.current_view.show()
        input_events.listener_add('browser-' + self.name, self._input_event_cb)
        for b in _topbar_btns:
            b.focus_allow = True

    def hide(self):
        """ TODO Function doc """
        gui.signal_emit('topbar,hide')
        input_events.listener_del('browser-' + self.name)
        self.current_view.hide()
        for b in _topbar_btns:
            b.focus_allow = False

    @property
    def freezed(self):
        """ TODO Function doc """
        return self._freezed

    def freeze(self):
        """ TODO Function doc """
        self.autoselect_url = self.current_view.selected_url_get()
        self.hide()
        mainmenu.show()
        self._freezed = True

    def unfreeze(self):
        """ TODO Function doc """
        mainmenu.hide()
        self._populate_page(self.pages[-1], is_unfreeze=True)
        self.show()
        self._freezed = False

    def item_bring_in(self, pos='top', animated=True):
        """ Move the view so that the currently selected item will go on 'pos'
        pos can be: 'in', ', top', 'mid'
        """
        self.current_view.item_bring_in(pos, animated)

    # private stuff
    def _populate_page(self, page, is_back=False, is_refresh=False, is_unfreeze=False):
        full = ' > '.join([p['title'] for p in self.pages])
        gui.text_set('topbar.title', full)

        view = page['view']
        if view == self.current_view:
            # same style for the 2 pages, ask the view to perform the correct anim
            if is_refresh or is_unfreeze:
                view.page_show(page['title'], ANIM_NONE)
            elif is_back:
                view.page_show(page['title'], ANIM_BACK)
            elif len(self.pages) < 2:
                view.page_show(page['title'], ANIM_NONE)
            else:
                view.page_show(page['title'], ANIM_FORWARD)
        else:
            # different style...hide one view and show the other
            self.current_view.clear()
            self.current_view.hide()
            view.page_show(page['title'], ANIM_NONE)
            view.show()

        # update state
        self.current_view = view

        # back item (optional)
        if ini.get_bool('general', 'back_in_lists') is True:
            self.item_add(BackItemClass(), 'emc://back', self)
            view.items_count -= 1

        # use this for extra debug
        # print(self)

        # and finally populate the page
        url = page['url']
        cb = page['cb']
        args = page['args']
        kwargs = page['kwargs']
        cb(self, url, *args, **kwargs)

    def _input_event_cb(self, event):
        # pass the event to the view
        view_ret = self.current_view.input_event_cb(event)
        if view_ret == input_events.EVENT_BLOCK:
            return input_events.EVENT_BLOCK

        # always
        if event == 'BACK':
            self.back()
            return input_events.EVENT_BLOCK
        if event == 'EXIT':
            self.freeze()
            return input_events.EVENT_BLOCK
        elif event == 'VIEW_LIST':
            self.change_style('List')
            return input_events.EVENT_BLOCK
        elif event == 'VIEW_POSTERGRID':
            self.change_style('PosterGrid')
            return input_events.EVENT_BLOCK
        elif event == 'VIEW_COVERGRID':
            self.change_style('CoverGrid')
            return input_events.EVENT_BLOCK

        return input_events.EVENT_CONTINUE

    @staticmethod
    def _create_or_get_view(view_name):
        if view_name in _views:
            DBG('View exists: ' + view_name)
            return _views[view_name]
        # call the constructor of the given class style ex: ViewList()
        DBG('Create view: ' + view_name)
        _views[view_name] = eval('View' + view_name + '()')
        return _views[view_name] class EmcItemClass(Singleton):
    """ TODO Class doc """

    def item_selected(self, url, user_data):
        """ Called when an item is selected """
        # DBG(('item_selected(%s)' % url))
        pass

    def label_get(self, url, user_data):
        """ Called when a view need to show the label of your item.
            Must return the string to show. """
        # DBG(('label_get(%s)' % url))
        return 'Unknow'

    def label_end_get(self, url, user_data):
        """ Called when a view need to show the secondary label of your item.
            Must return the string to show, or None """
        return None

    def icon_get(self, url, user_data):
        """ Called when a view need to show the icon of your item.
            Must return the name of the icon to use for the given url
            see gui.load_icon() for detail on what you can pass as the name"""
        # DBG(('icon_get(%s)' % url))
        return None

    def icon_end_get(self, url, user_data):
        # DBG(('icon_end_get(%s)' % url))
        return None

    def info_get(self, url, user_data):
        """ Called when a view need to show the info of your item.
            Must return a string with the murkupped text that describe the item """
        # DBG(('info_get(%s)' % url))
        return None

    def poster_get(self, url, user_data):
        """ Called when a view need to show the poster of your
            item, must return the full path of a valid image file.
            The poster MUST have a 1:2 aspect.
            You can also return a valid url (http://) to automatically
            download the image to a random temp file.
            In addition you can also set the destination path for the given url,
            to set an url AND a destination just return a tuple, as:
            (url, local_path)
            """
        # DBG(('poster_get(%s)' % url))
        return None

    def cover_get(self, url, user_data):
        """ Like poster_get but th image should be squared (or a bit larger) """
        # DBG(('poster_get(%s)' % url))
        return None

    def fanart_get(self, url, user_data):
        """ Called when a view need to show the fanart of your item.
            Must return the full path of a valid image file """
        # DBG(('fanart_get(%s)' % url))
        return None class FolderItemClass(EmcItemClass):
    """ Base item class to be subclassed for ALL folder items """
    def item_selected(self, url, user_data):
        raise NotImplementedError

    def label_get(self, url, user_data):
        return elm.utf8_to_markup(os.path.basename(url))

    def icon_get(self, url, user_data):
        return 'icon/folder'

    def poster_get(self, url, user_data):
        return 'special/folder/' + elm.utf8_to_markup(os.path.basename(url)) class BackItemClass(EmcItemClass):
    def item_selected(self, url, user_data):
        user_data.back()

    def label_get(self, url, user_data):
        return _('Back')

    def icon_get(self, url, user_data):
        return 'icon/back' class MusicBrainz(object):
    """ TODO doc """

    def __init__(self, show_gui=True):
        self._show_gui = show_gui
        self._wait_dialog = None

    @staticmethod
    def get_cdrom_discid(device_path):
        """ Retrive the discid from the disc in the drive

        Args:
           device_path:  cdrom device that must contain a valid AudioCD

        Return:
           The extracted discid (str), ex: "tULuQ98H70kkMmAtHFR7jKlxGRU-"

        """
        try:
            # TODO: this is a bit slow... should be async'ed in some way?
            disc = discid.read(device_path, features=[])
        except discid.DiscError:
            return None
        else:
            return disc.id

    def get_cdrom_info(self, device_path, info_cb, ignore_cache=False, **kargs):
        """ Fetch cdrom album info from the MusicBrainz service

        Args:
           device_path: cdrom device that must contain a valid AudioCD
           info_cb: user function to call when operation completed
                    sig: func(album_data, **kargs) album_data is None on errors
           ignore_cache: do not lookup in cache
           **kargs: any other keyword arguments will be passed back in the info_cb

        Return:
           True if the operation started successfully, info_cb will be called later
           False in case of errors
           dict (album_data) if the info was already fatched and cached

        """
        # exctract discid
        disc_id = self.get_cdrom_discid(device_path)
        if disc_id is None:
            return False
        DBG('discid', disc_id)

        # TODO use an emc db to cache results

        # MusicBrainz discid query
        self._show_wait_dialog()
        url = '{}/discid/{}?fmt=json&inc=artists+recordings'.format(API_BASE, disc_id)
        utils.EmcUrl(url, headers=API_HEADS, done_cb=self._cdrom_info_done_cb,
                     disc_id=disc_id, user_cb=info_cb, user_kargs=kargs)
        return True

    def _cdrom_info_done_cb(self, url, status, data, disc_id, user_cb, user_kargs):
        self._destroy_wait_dialog()
        if status != 200:
            user_cb(None, **user_kargs)
            return

        # parse the json response
        json_data = json.loads(data)
        if not json_data or 'error' in json_data:
            user_cb(None, **user_kargs)
            return

        # consider only in the first release (album) found
        try:
            rel = json_data['releases'][0]
        except KeyError:
            user_cb(None, **user_kargs)
            return

        # basic album info
        mbzid = rel.get('id')
        title = rel.get('title')
        date = rel.get('date')
        country = rel.get('country')

        # artists names
        artists = []
        if 'artist-credit' in rel:
            for artist in rel['artist-credit']:
                if 'name' in artist:
                    artists.append(artist['name'])

        # track titles + durations
        tracks = []
        if 'media' in rel and len(rel['media']) > 0 and 'tracks' in rel['media'][0]:
            for trk in rel['media'][0]['tracks']:
                track_data = {
                    'title': trk.get('title'),
                    'length': (trk['length'] / 1000) if 'length' in trk else 0,
                    'num': int(trk.get('number', -1))
                }
                tracks.append(track_data)

        # cover-art url (search in ALL releases)
        cover_url = None
        for r in json_data['releases']:
            if mbzid and 'cover-art-archive' in r:
                archive = r['cover-art-archive']
                if archive.get('artwork') is True and archive.get('front') is True:
                    cover_url = '{}/release/{}/front-500'.format(ART_BASE, r['id'])

        # build our simple data storage
        album = {
            'mbzid': mbzid,
            'discid': disc_id,
            'title': title,
            'date': date,
            'artists': artists,
            'cover_url': cover_url,
            'tracks': tracks,
        }
        DBG(album)

        # and finally call the user callback
        user_cb(album, **user_kargs)

    def _show_wait_dialog(self):
        if self._show_gui:
            self._wait_dialog = EmcDialog(style='minimal',
                                          title=_('Fetching info'),
                                          content=EmcImage('image/musicbrainz_logo.png'),
                                          spinner=True)

    def _destroy_wait_dialog(self):
        if self._wait_dialog is not None:
            self._wait_dialog.delete()
            self._wait_dialog = None
import os
import time
import pprint
import epymc.mainmenu as mainmenu
import epymc.utils as utils
import epymc.events as events
import epymc.ini as ini
import epymc.gui as gui
import epymc.mediaplayer as mediaplayer
import epymc.storage as storage
import epymc.browser as browser
from efl import ecore, edje, elementary
from efl.elementary.box import Box
from efl.elementary.grid import Grid
from efl.elementary.entry import utf8_to_markup
from efl.evas import EXPAND_BOTH, FILL_BOTH, FILL_HORIZ
from epymc.modules import EmcModule
from epymc.gui import EmcDialog, EmcVKeyboard, EmcFolderSelector, \
    EmcButton, EmcNotify, EmcMenu, DownloadManager, EmcSlider
from epymc.browser import EmcBrowser, EmcItemClass, FolderItemClass, BackItemClass
from epymc.musicbrainz import MusicBrainz
        from epymc.extapi.onlinevideo import fetch_url, call_ydl, url_encode
        elif url == 'uitests://images':
            _mod._browser.page_add('uitests://images', 'Image tests',
                                   ('List', 'PosterGrid'),
                                   _mod.populate_image_page)

        elif url == 'uitests://views':
            _mod._browser.page_add('uitests://views', 'Browser Views',
                                   ('List', 'PosterGrid', 'CoverGrid'),
                                   _mod.populate_views_page)

        # Events Sniffer
        elif url == 'uitests://sniffer':
            events.listener_add('sniffer', lambda ev: EmcNotify('<title>Event sniffer</><br>' + ev))
            n = EmcNotify('Sniffer enabled.', hidein=2)

        # Event Emit
        elif url == 'uitests://ev_emit':
            events.event_emit('TEST_EVENT')

        # Notify
        elif url == 'uitests://notify':
            EmcNotify('<title>Title 1</><br>'
                      'Without icon.<br>'
                      'Will hide in 10 seconds.',
                      hidein=20)
            EmcNotify('<title>Title 2</><br>'
                      'This one with an image.',
                      icon='dvd_cover_blank.png')
            EmcNotify('<title>Title 3</><br>'
                      'This one with an icon',
                      icon='icon/movie')
            EmcNotify('<title>Title 4</><br>'
                      'Test longer text and tags.<br>'
                      '<b>bold</b> <i>italic</i> <u>underline</u> <link>link</link> '
                      '<info>info</info> <success>success</success> '
                      '<warning>warning</warning> <failure>failure</failure>.',
                      icon='icon/movie')

        # Menu
        elif url == 'uitests://menu':
            def _cb_menu(menu, item):
                print("Selected item: " + item.text)

            m = EmcMenu()
            m.item_add("Item 1", callback=_cb_menu)
            m.item_add("Item 2", callback=_cb_menu)
            m.item_add("Item 3", callback=_cb_menu)
            m.item_separator_add()
            m.item_add("Item 4", "icon/evas", callback=_cb_menu)
            m.item_add("Item 5", "icon/home", "icon/volume", callback=_cb_menu)
            m.item_separator_add()
            it = m.item_add("Disabled", callback=_cb_menu)
            it.disabled = True
            it = m.item_add("Disabled", 'icon/home', callback=_cb_menu)
            it.disabled = True
            m.item_add("Item 8", None, 'icon/volume', callback=_cb_menu)
            it = m.item_add("Item 9 (disabled)", callback=_cb_menu)
            it.disabled = True
            m.show()

        elif url == 'uitests://menu_long':
            def _cb_menu(menu, item):
                print('Selected item: ' + item.text)

            m = EmcMenu(dismiss_on_select=False)
            for i in range(1, 100):
                m.item_add('Item %d' % i, 'icon/home', 'icon/volume', callback=_cb_menu)
            m.show()

        # TMDB
        # elif url == 'uitests://tmdb':
        # s = TMDB_WithGui()
        # s.movie_search('alien')

        # Download Manager
        elif url == 'uitests://dm':
            DownloadManager().queue_download('http://fredrik.hubbe.net/plugger/xvidtest.avi', 'dm_test1')
            DownloadManager().queue_download(
                'http://www.archive.org/download/TheMakingOfSuzanneVegasSecondLifeGuitar/3-TheMakingOfSuzanneVega_sSecondLifeGuitar.mp4',
                'TheMakingOfSuzanneVega')

        elif url == 'uitests://dm2':
            DownloadManager().in_progress_show()

        # Mediaplayer Local Video
        elif url == 'uitests://mpv':
            f = os.path.expanduser('~/Video/testvideo.avi')
            # f = os.path.expanduser('~/Video/testvideo.mp4')
            mediaplayer.play_url(f)  # , start_from=0)
            mediaplayer.title_set('Testing title')
            mediaplayer.poster_set('image/dvd_cover_blank.png')

        # Mediaplayer Online Video (good)
        # elif url == 'uitests://mpvo':
        # mediaplayer.play_url('http://trailers.apple.com/movies/independent/airracers/airracers-tlr1_h480p.mov')

        # http://samples.mplayerhq.hu/
        # http://download.wavetlan.com/SVV/Media/HTTP/http-mp4.htm

        # Mediaplayer Online Video (med)
        elif url == 'uitests://mpvom':
            mediaplayer.play_url('http://fredrik.hubbe.net/plugger/xvidtest.avi')

        # Mediaplayer Online Video (bad)
        elif url == 'uitests://mpvob':
            mediaplayer.play_url(
                'http://www.archive.org/download/TheMakingOfSuzanneVegasSecondLifeGuitar/3-TheMakingOfSuzanneVega_sSecondLifeGuitar.mp4')

        # Mediaplayer DVD
        elif url == 'uitests://dvd':
            mediaplayer.play_url('dvd:///dev/cdrom')

        # VKeyboard
        elif url == 'uitests://vkbd':
            EmcVKeyboard(title='Virtual Keyboard', text='This is the keyboard test!',
                         accept_cb=lambda vk, t: print('ACCEPT "%s"' % t),
                         dismiss_cb=lambda vk: print('DISMISS'))

        # Source Selector
        elif url == 'uitests://sselector':

            EmcFolderSelector(title='Folder Selector Test',