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

    def __init__(self):
        pass

    def __shutdown__(self):
        pass

    def __restart__(self):
        self.__shutdown__()
        self.__init__() 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 EmcExec(object):
    """
    Just a tiny wrapper around ecore.Exe to execute shell command async
    cmd: the command to execute
    grab_output: whenever to collect the stdoutput
    done_cb: function to call when the program ends. Will receive one argument:
             the standard output of the command or an empty string if
             grab_input is False (the default)
             done_cb will also receive any other params you pass to the costructor
    """

    def __init__(self, cmd, grab_output=False, done_cb=None, *args, **kargs):
        self.done_cb = done_cb
        self.args = args
        self.kargs = kargs
        self.grab_output = grab_output
        self.outbuffer = ''
        if grab_output:
            self.exe = ecore.Exe(cmd,
                                 ecore.ECORE_EXE_PIPE_READ |
                                 ecore.ECORE_EXE_PIPE_READ_LINE_BUFFERED |
                                 ecore.ECORE_EXE_TERM_WITH_PARENT)
            self.exe.on_data_event_add(self.data_cb)
        else:
            self.exe = ecore.Exe(cmd, ecore.ECORE_EXE_TERM_WITH_PARENT)
        if done_cb:
            self.exe.on_del_event_add(self.del_cb)

    def data_cb(self, exe, event):
        for line in event.lines:
            self.outbuffer += (line + '\n')

    def del_cb(self, exe, event):
        if callable(self.done_cb):
            self.done_cb(self.outbuffer, *self.args, **self.kargs) def download_url_async(url, dest='tmp', min_size=0,
                       complete_cb=None, progress_cb=None,
                       urlencode=True, *args, **kargs):
    """Download the given url in async way.

    TODO:
       If dest is set to None than the data will be passed as the dest param
       in the complete_cb.

    Args:
       url: must be a valid url to download.
       dest: If set to a local file name then the download data will be written
          to that file (created and overwritten if necessary, also the
          necessary parent directories are created).
          If dest is omitted (or is 'tmp') than the data will be written
          to a random new temp file.
       min_size: if min_size is set (and > 0) than downloaded files smaller that
          min_size will be discarted.
       complete_cb: if given, will be called when the download is done.
          signature: complete_cb(file, status, *args, **kargs)
       progress_cb: will be called while the download is in progress.
          signature: progress_cb(file, dltotal, dlnow, *args, **kargs)
       urlencode: encode the given url (default to True).
       *args: any other arguments will be passed back in the callbacks.
       **kargs: any other keyword arguments will be passed back in the callbacks.

    Returns:
       The Ecore FileDownload instance

    """

    def _cb_download_complete(dest, status, dwl_data, *args, **kargs):
        (complete_cb, progress_cb, min_size) = dwl_data

        # if file size < min_size: report as error
        if status == 200 and min_size > 0 and os.path.getsize(dest) < min_size:
            DBG('MIN_SIZE not reached, discard download')
            status = 404  # HTTP NotFound code

        # on errors delete the downloaded file
        if status != 200 and os.path.exists(dest):
            DBG('download error, HTTP code: ' + str(status))
            os.remove(dest)

        # call the user complete_cb if available
        if complete_cb and callable(complete_cb):
            complete_cb(dest, status, *args, **kargs)

    def _cb_download_progress(dest, dltotal, dlnow, uptotal, upnow, dwl_data, *args, **kargs):
        (complete_cb, progress_cb, min_size) = dwl_data
        # TODO filter out some call (maybe report only when dlnow change)
        if progress_cb and callable(progress_cb):
            progress_cb(dest, dltotal, dlnow, *args, **kargs)
        return 0  # always continue the download

    # urlencode the url (but not the http:// part, or ':' will be converted)
    if urlencode:
        (_prot, _url) = url.split('://', 1)
        encoded = '://'.join((_prot, urllib_quote(_url)))
    else:
        encoded = url

    # use a random temp file
    if dest == 'tmp':
        dest = tempfile.mktemp()
    elif dest:
        # create dest path if necessary,
        dirname = os.path.dirname(dest)
        if not os.path.exists(dirname):
            os.makedirs(dirname)
        # remove destination file if exists (overwrite)
        if os.path.exists(dest):
            os.remove(dest)

    # store download data for later use
    dwl_data = (complete_cb, progress_cb, min_size)

    # start the download
    return ecore.FileDownload(encoded, dest,
                              _cb_download_complete, _cb_download_progress,
                              dwl_data=dwl_data, *args, **kargs) 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 YoutubeDL(object):
    """ Helper class to interact with the youtube-dl executable """

    def __init__(self):
        self.exe = os.path.join(utils.user_cache_dir, 'youtube-dl')
        self._update_dialog = None
        self._done_cb = None
        self._done_cb_kargs = None
        self._url_dialog = None

    @property
    def installed(self):
        """ True if youtube-dl is already installed """
        return os.path.exists(self.exe)

    def get_real_video_url(self, url, done_cb, **kargs):
        """ Scrape the given url using youtube-dl
        Args:
           url: any url supported by youtube-dl
           done_cb: function to call when the process is completed
              signature: func(real_url, **kargs)
           **kargs: any other keyword arguments will be passed back in done_cb
        """
        DBG('Getting real url for:', url)
        self._done_cb = done_cb
        self._done_cb_kargs = kargs
        txt = _('Please wait while searching the video...') + '<br><br>' + \
              _('For info and credits please visit:') + '<br>' + \
              '<info>rg3.github.io/youtube-dl</>'
        self._url_dialog = gui.EmcDialog(style='minimal', title=_('Youtube-DL'),
                                         text=txt, spinner=True)
        utils.EmcExec('{} --get-url --format best "{}"'.format(self.exe, url),
                      grab_output=True, done_cb=self._get_real_video_url_cb)

    def _get_real_video_url_cb(self, cmd_output):
        self._url_dialog.delete()
        self._done_cb(cmd_output, **self._done_cb_kargs)

    def check_update(self, verbose=True, quiet=False, done_cb=None, **kargs):
        """ Check if a newer version is available and download if needed

        Args:
           verbose (bool): Show the progress dialog also while checking versions
           quiet (bool): Do not show any dialog at all
           done_cb: function to call when the process is completed
              signature: func(success, dialog, **kargs)
           **kargs: any other keyword arguments will be passed back in done_cb
        """
        self._local_version = None
        self._remote_version = None
        self._update_dialog = None
        self._update_text = ''
        self._quiet_update = quiet
        self._done_cb = done_cb
        self._done_cb_kargs = kargs

        txt = _('Checking for updates, please wait.') + '<br>'
        if verbose:
            self._update_dialog = gui.EmcDialog(style='progress', text=txt,
                                                title=_('Youtube-DL'))
        else:
            self._update_text += txt

        # check local version...
        utils.EmcExec('{} --version'.format(self.exe), grab_output=True,
                      done_cb=self._local_vers_cb)
        # check remote version...
        utils.EmcUrl('http://youtube-dl.org/latest/version',
                     done_cb=self._remote_vers_cb)

    def _local_vers_cb(self, version):
        self._local_version = version.strip() if version else _('Unknown')
        DBG('Local version:', self._local_version)
        txt = '<name>{}:</name> {}<br>'.format(_('Local version'),
                                               self._local_version)
        if self._update_dialog:
            self._update_dialog.text_append(txt)
        else:
            self._update_text += txt
        self._local_or_remote_done()

    def _remote_vers_cb(self, url, status, version):
        self._remote_version = version.strip() if status == 200 else _('Unknown')
        DBG('Upstream version:', self._remote_version)
        txt = '<name>{}:</name> {}<br>'.format(_('Upstream version'),
                                               self._remote_version)
        if self._update_dialog:
            self._update_dialog.text_append(txt)
        else:
            self._update_text += txt
        self._local_or_remote_done()

    def _local_or_remote_done(self):
        if self._local_version is None or self._remote_version is None:
            return

        if self._local_version == _('Unknown') or \
                self._remote_version == _('Unknown') or \
                self._remote_version != self._local_version:
            self._download_latest()
            return

        txt = '<success>{}</success>'.format(_('Already updated'))
        if self._update_dialog:
            self._update_dialog.text_append(txt)
            self._update_dialog.progress_set(1.0)
        else:
            self._update_text += txt
        if callable(self._done_cb):
            self._done_cb(True, self._update_dialog, **self._done_cb_kargs)
        elif self._update_dialog:
            self._update_dialog.delete()

    def _download_latest(self):
        txt = '<info>{}</info><br>'.format(_('Updating to latest release...'))
        if self._update_dialog:
            self._update_dialog.text_append(txt)
        elif self._quiet_update is False:
            self._update_text += txt
            self._update_dialog = gui.EmcDialog(style='progress',
                                                title=_('Youtube-DL'),
                                                text=self._update_text)

        utils.download_url_async('http://youtube-dl.org/latest/youtube-dl',
                                 dest=self.exe + '.temp',
                                 progress_cb=self._dwn_progress_cb,
                                 complete_cb=self._dwn_complete_cb)

    def _dwn_progress_cb(self, dest, dltotal, dlnow):
        if self._update_dialog:
            self._update_dialog.progress_set((dlnow / dltotal) if dltotal else 0)

    def _dwn_complete_cb(self, dest, status):
        if status == 200:
            os.chmod(dest, 0o0744)  # (make it executable)
            os.rename(dest, self.exe)  # (atomically remove ".temp")
            txt = '<success>{}</success>'.format(_('Download completed'))
            if self._update_dialog:
                self._update_dialog.text_append(txt)
            else:
                self._update_text += txt
        else:
            DBG("ERROR: download failed")
            txt = '<failure>{}</failure>'.format(_('Download failed'))
            if self._update_dialog:
                self._update_dialog.text_append(txt)
            else:
                self._update_text += txt
        if callable(self._done_cb):
            self._done_cb(True if status == 200 else False, self._update_dialog,
                          **self._done_cb_kargs)
        elif self._update_dialog:
            self._update_dialog.delete() ACT_DEFAULT = 0 ACT_NONE = 1 ACT_FOLDER = 2 ACT_MORE = 3 ACT_PLAY = 4 ACT_SEARCH = 5
import os
import sys
import ast
import configparser
import epymc.mainmenu as mainmenu
import epymc.mediaplayer as mediaplayer
import epymc.utils as utils
import epymc.gui as gui
import epymc.ini as ini
import epymc.events as events
import epymc.config_gui as cgui
from efl import evas, elementary
from epymc.modules import EmcModule
from epymc.browser import EmcBrowser, EmcItemClass
from epymc.utils import EmcExec, download_url_async
from epymc.gui import EmcDialog, EmcVKeyboard
from epymc.youtubedl import YoutubeDL
from epymc.extapi.onlinevideo import ACT_DEFAULT, ACT_NONE, ACT_FOLDER, \
    ACT_MORE, ACT_PLAY, ACT_SEARCH
#!/usr/bin/env python
# This Python file uses the following encoding: utf-8
#
# Copyright (C) 2010-2018 Davide Andreoli <dave@gurumeditation.it>
#
# This file is part of EpyMC, an EFL based Media Center written in Python.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

from __future__ import absolute_import, print_function







def DBG(msg):
    # print('ONLINEVID: %s' % msg)
    pass


F_STATE = 0
F_LABEL = 1
F_URL = 2
F_INFO = 3
F_ICON = 4
F_POSTER = 5
F_ACTION = 6

_mod = None



class ChannelItemClass(EmcItemClass):