def _init_dialog(self):
        self.dialog = ExtendedMessageDialog(self.main_controller.main_window, 0,
                                Gtk.MessageType.INFO, Gtk.ButtonsType.NONE,
                                "", title="download log - " + get_iplayer_downloader.PROGRAM_NAME)

        label = self.dialog.get_scrolled_label()
        label.set_valign(Gtk.Align.START)
        label.set_halign(Gtk.Align.START)
        label.set_selectable(True)
        
        #label.override_font(Pango.FontDescription("monospace small"))
        label.override_font(Pango.FontDescription("monospace 10"))
        #ALTERNATIVE
        #css_provider = Gtk.CssProvider()
        #css_provider.load_from_data(b""" * { font: monospace; font-size: 10; } """)
        #context = label.get_style_context()
        #context.add_provider(css_provider, Gtk.STYLE_PROVIDER_PRIORITY_USER)

        self.dialog.add_button("Clear log and cache", CLEAR_CACHE_BUTTON_ID)
        self.dialog.add_button("Reset error count", RESET_ERROR_COUNT_BUTTON_ID)
        self.dialog.add_button("Detailed log", FULL_LOG_BUTTON_ID)
        self.dialog.add_button("Log", SUMMARY_LOG_BUTTON_ID)
        self.dialog.add_button(Gtk.STOCK_CLOSE, Gtk.ResponseType.CLOSE)
        
        # Dialog buttons are laid out from left to right
        button = self.dialog.get_action_area().get_children()[0]
        #button.set_image(Gtk.Image(icon_name=Gtk.STOCK_DELETE))
        button.set_tooltip_text("Remove log files and image cache files")
        button = self.dialog.get_action_area().get_children()[1]
        #button.set_image(Gtk.Image(icon_name=Gtk.STOCK_CLEAR))
        button.set_tooltip_text("Reset error count in the progress bar")
        button = self.dialog.get_action_area().get_children()[2]
        button.set_image(Gtk.Image(icon_name=Gtk.STOCK_REFRESH))
        button.set_tooltip_text("Refresh today's download log")
        button = self.dialog.get_action_area().get_children()[3]
        button.set_image(Gtk.Image(icon_name=Gtk.STOCK_REFRESH))
        button.set_tooltip_text("Refresh today's summary download log")
        #button.grab_focus()
        # Close button
        button = self.dialog.get_action_area().get_children()[4]
        button.grab_focus()
        
        self.dialog.set_default_response(Gtk.ResponseType.CLOSE)
        #self.dialog.format_secondary_text("")
        self.dialog.get_content_area().set_size_request(get_iplayer_downloader.ui.main_window.WINDOW_LARGE_WIDTH,
                                                        get_iplayer_downloader.ui.main_window.WINDOW_LARGE_HEIGHT)
    def _init_dialog(self):
        self.dialog = ExtendedMessageDialog(
            self.main_controller.main_window,
            0,
            Gtk.MessageType.INFO,
            Gtk.ButtonsType.NONE,
            "",
            title="download log - " + get_iplayer_downloader.PROGRAM_NAME,
        )

        label = self.dialog.get_scrolled_label()
        label.set_valign(Gtk.Align.START)
        label.set_halign(Gtk.Align.START)
        label.set_selectable(True)

        # label.override_font(Pango.FontDescription("monospace small"))
        label.override_font(Pango.FontDescription("monospace 10"))
        # ALTERNATIVE
        # css_provider = Gtk.CssProvider()
        # css_provider.load_from_data(b""" * { font: monospace; font-size: 10; } """)
        # context = label.get_style_context()
        # context.add_provider(css_provider, Gtk.STYLE_PROVIDER_PRIORITY_USER)

        self.dialog.add_button("Clear log and cache", CLEAR_CACHE_BUTTON_ID)
        self.dialog.add_button("Reset error count", RESET_ERROR_COUNT_BUTTON_ID)
        self.dialog.add_button("Detailed log", FULL_LOG_BUTTON_ID)
        self.dialog.add_button("Log", SUMMARY_LOG_BUTTON_ID)
        self.dialog.add_button(Gtk.STOCK_CLOSE, Gtk.ResponseType.CLOSE)

        # Dialog buttons are laid out from left to right
        button = self.dialog.get_action_area().get_children()[0]
        # button.set_image(Gtk.Image(icon_name=Gtk.STOCK_DELETE))
        button.set_tooltip_text("Remove log files and image cache files")
        button = self.dialog.get_action_area().get_children()[1]
        # button.set_image(Gtk.Image(icon_name=Gtk.STOCK_CLEAR))
        button.set_tooltip_text("Reset error count in the progress bar")
        button = self.dialog.get_action_area().get_children()[2]
        button.set_image(Gtk.Image(icon_name=Gtk.STOCK_REFRESH))
        button.set_tooltip_text("Refresh today's download log")
        button = self.dialog.get_action_area().get_children()[3]
        button.set_image(Gtk.Image(icon_name=Gtk.STOCK_REFRESH))
        button.set_tooltip_text("Refresh today's summary download log")
        # button.grab_focus()
        # Close button
        button = self.dialog.get_action_area().get_children()[4]
        button.grab_focus()

        self.dialog.set_default_response(Gtk.ResponseType.CLOSE)
        # self.dialog.format_secondary_text("")
        self.dialog.get_content_area().set_size_request(
            get_iplayer_downloader.ui.main_window.WINDOW_LARGE_WIDTH,
            get_iplayer_downloader.ui.main_window.WINDOW_LARGE_HEIGHT,
        )
class LogViewerDialogWrapper(object):

    def __init__(self, main_controller):
        self.main_controller = main_controller
        
        self._init_dialog()
        
    def _init_dialog(self):
        self.dialog = ExtendedMessageDialog(self.main_controller.main_window, 0,
                                Gtk.MessageType.INFO, Gtk.ButtonsType.NONE,
                                "", title="download log - " + get_iplayer_downloader.PROGRAM_NAME)

        label = self.dialog.get_scrolled_label()
        label.set_valign(Gtk.Align.START)
        label.set_halign(Gtk.Align.START)
        label.set_selectable(True)
        
        #label.override_font(Pango.FontDescription("monospace small"))
        label.override_font(Pango.FontDescription("monospace 10"))
        #ALTERNATIVE
        #css_provider = Gtk.CssProvider()
        #css_provider.load_from_data(b""" * { font: monospace; font-size: 10; } """)
        #context = label.get_style_context()
        #context.add_provider(css_provider, Gtk.STYLE_PROVIDER_PRIORITY_USER)

        self.dialog.add_button("Clear log and cache", CLEAR_CACHE_BUTTON_ID)
        self.dialog.add_button("Reset error count", RESET_ERROR_COUNT_BUTTON_ID)
        self.dialog.add_button("Detailed log", FULL_LOG_BUTTON_ID)
        self.dialog.add_button("Log", SUMMARY_LOG_BUTTON_ID)
        self.dialog.add_button(Gtk.STOCK_CLOSE, Gtk.ResponseType.CLOSE)
        
        # Dialog buttons are laid out from left to right
        button = self.dialog.get_action_area().get_children()[0]
        #button.set_image(Gtk.Image(icon_name=Gtk.STOCK_DELETE))
        button.set_tooltip_text("Remove log files and image cache files")
        button = self.dialog.get_action_area().get_children()[1]
        #button.set_image(Gtk.Image(icon_name=Gtk.STOCK_CLEAR))
        button.set_tooltip_text("Reset error count in the progress bar")
        button = self.dialog.get_action_area().get_children()[2]
        button.set_image(Gtk.Image(icon_name=Gtk.STOCK_REFRESH))
        button.set_tooltip_text("Refresh today's download log")
        button = self.dialog.get_action_area().get_children()[3]
        button.set_image(Gtk.Image(icon_name=Gtk.STOCK_REFRESH))
        button.set_tooltip_text("Refresh today's summary download log")
        #button.grab_focus()
        # Close button
        button = self.dialog.get_action_area().get_children()[4]
        button.grab_focus()
        
        self.dialog.set_default_response(Gtk.ResponseType.CLOSE)
        #self.dialog.format_secondary_text("")
        self.dialog.get_content_area().set_size_request(get_iplayer_downloader.ui.main_window.WINDOW_LARGE_WIDTH,
                                                        get_iplayer_downloader.ui.main_window.WINDOW_LARGE_HEIGHT)

    def run(self):
        button_id_prev = Gtk.ResponseType.CLOSE
        button_id = SUMMARY_LOG_BUTTON_ID
        full = False
        while True:
            if button_id == FULL_LOG_BUTTON_ID or button_id == SUMMARY_LOG_BUTTON_ID:
                full = (button_id == FULL_LOG_BUTTON_ID)
            if full:
                message_format = "Detailed Download Log"
            else:
                message_format = "Download Log"
            markup = not full
            log_output = command_util.download_log(full=full, markup=markup)

            # Set dialog content title
            self.dialog.set_property("text", message_format)
            # Set dialog content text
            #NOTE If full download log text is too large, it won't be displayed
            if markup:
                self.dialog.format_tertiary_scrolled_markup(log_output)
            else:
                self.dialog.format_tertiary_scrolled_text(log_output)
            
            # Grab focus to enable immediate page-up/page-down scrolling with the keyboard
            #label = self.dialog.get_scrolled_label()
            #scrolled_window = label.get_parent().get_parent()
            #scrolled_window.grab_focus()
            
            if button_id == FULL_LOG_BUTTON_ID or button_id == SUMMARY_LOG_BUTTON_ID:
                if button_id_prev != button_id:
                    # Log view changed (different log view type or log files removed)
                    # Scroll to top
                    label = self.dialog.get_scrolled_label()
                    adjustment = label.get_parent().get_vadjustment()
                    adjustment.set_value(0.0)
                    adjustment.value_changed()
                    #adjustment = label.get_parent().set_vadjustment(adjustment)
            
            if button_id != RESET_ERROR_COUNT_BUTTON_ID:
                # No need to track RESET_ERROR_COUNT_BUTTON_ID because it doesn't affect the log view
                button_id_prev = button_id
                
            button_id = self.dialog.run()

            if button_id == CLEAR_CACHE_BUTTON_ID:
                command_util.clear_cache()
                self.main_controller.on_progress_bar_update(None)
            elif button_id == RESET_ERROR_COUNT_BUTTON_ID:
                self.main_controller.invalidate_error_offset()
            elif button_id == Gtk.ResponseType.CLOSE or button_id == Gtk.ResponseType.DELETE_EVENT:
                break
            
    def destroy(self):
        #if self.dialog is not None:
        self.dialog.destroy()
    def on_button_similar_clicked(self, button, locate_search_term):
        # button can be None

        if os.name == "posix" and locate_search_term is not None:
            output = ""

            self.main_window.display_busy_mouse_cursor(True)
            cmd = "locate " + file.sanitize_path(locate_search_term, False)
            process_output = command.run(cmd, quiet=True)
            output += cmd + ":\n" + process_output
            self.main_window.display_busy_mouse_cursor(False)

            dialog = ExtendedMessageDialog(self.main_window, 0,
                                           Gtk.MessageType.INFO,
                                           Gtk.ButtonsType.CLOSE,
                                           "Similar Episodes")
            #dialog.format_secondary_text("")
            dialog.set_default_response(Gtk.ResponseType.CLOSE)
            dialog.get_content_area().set_size_request(
                get_iplayer_downloader.ui.main_window.WINDOW_LARGE_WIDTH_WIDE,
                get_iplayer_downloader.ui.main_window.WINDOW_LARGE_HEIGHT_WIDE)

            dialog.format_tertiary_scrolled_text(output)
            label = dialog.get_scrolled_label()
            label.set_valign(Gtk.Align.START)
            label.set_halign(Gtk.Align.START)
            label.set_selectable(True)
            #label.override_font(Pango.FontDescription("monospace small"))
            label.override_font(Pango.FontDescription("monospace 10"))
            dialog.run()
            dialog.destroy()
    def on_button_download_clicked(self, button, pvr_queue=False):
        # button can be None
        preset = None
        prog_type = None
        alt_recording_mode = False
        combo = self.tool_bar_box.preset_combo
        tree_iter = combo.get_active_iter()
        if tree_iter is not None:
            model = combo.get_model()

            #NOTE Downloading in "All" preset mode will not even work when all settings (radiomode, tvmode,
            #     outputradio, outputtv, etc.) are in one file (in the options file or a single preset file).
            #     Get_iplayer.get() cannot (easily) determine the prog_type of each episode and
            #     get_iplayer does not determine the programme type by it self
            #search_all = self.tool_bar_box.search_all_presets_check_button.get_active()
            #if search_all_presets:
            if string.str2bool(settings.get_config().get(
                    config.NOSECTION, "disable-presets")):
                preset = None
            else:
                preset = model[tree_iter][self.PresetComboModelColumn.PRESET]
            prog_type = model[tree_iter][self.PresetComboModelColumn.PROG_TYPE]
            #channel = model[tree_iter][self.PresetComboModelColumn.CHANNEL]

            alt_recording_mode = self.tool_bar_box.alt_recording_mode_check_button.get_active(
            )

        dry_run = self.tool_bar_box.dry_run_check_button.get_active()
        force = self.tool_bar_box.force_check_button.get_active()
        future = self.tool_bar_box.future_check_button.get_active()

        #PVR_CHECK_BUTTON
        #pvr_queue_checkbox_state = self.tool_bar_box.pvr_queue_check_button.get_active()
        #if button is not None and not pvr_queue:
        #    # If event was raised from the tool bar download button and not from a keyboard shortcut,
        #    # then the PVR check button determines the download/queue mode
        #    pvr_queue = pvr_queue_checkbox_state

        # Search selected leaf nodes (the second level) two levels deep
        model = self.main_tree_view.get_model()
        #indices = ""
        pid_list = []
        root_iter = model.get_iter_first()
        while root_iter is not None:
            row = model[root_iter]
            if row[SearchResultColumn.DOWNLOAD] and row[
                    SearchResultColumn.PID]:
                #indices += row[SearchResultColumn.INDEX] + " "
                pid_list.append(row[SearchResultColumn.PID])

            #if model.iter_has_child(root_iter):
            child_iter = model.iter_children(root_iter)
            while child_iter is not None:
                row = model[child_iter]
                if row[SearchResultColumn.DOWNLOAD]:
                    #indices += row[SearchResultColumn.INDEX] + " "
                    pid_list.append(row[SearchResultColumn.PID])
                child_iter = model.iter_next(child_iter)
            root_iter = model.iter_next(root_iter)

        #if not indices:
        if len(pid_list) == 0:
            dialog = Gtk.MessageDialog(self.main_window, 0,
                                       Gtk.MessageType.INFO,
                                       Gtk.ButtonsType.CLOSE,
                                       "No episodes selected")
            #dialog.format_secondary_text("")
            dialog.run()
            dialog.destroy()
            #return True
            return

        ####

        ## Avoid downloading an episode twice in parallel, otherwise continue downloading
        ## twice and let get_iplayer generate an "Already in history" INFO log message.
        ## The user can download episodes in parallel without having
        ## to clear the previous download selection and therefore avoiding
        ## download errors because of two threads trying to download the same episode
        #
        #try:
        #    if os.name == "posix":
        #        #NOTE 2>/dev/null: to surpress error messages, e.g.:
        #        #    Signal 18 (CONT) caught by ps (procps-ng version 3.3.9).
        #        #    ps:display.c:66: please report this bug
        #        gipd_processes = int(command.run("echo -n $(ps xo cmd 2>/dev/null | grep 'get_iplayer_downloader' | grep 'python' | grep -v 'grep' | wc -l) ; exit 0", quiet=True))
        #    else:
        #        gipd_processes = 1
        #except ValueError:
        #    # Sometimes gipd_processes is not a valid int (empty string?)
        #    gipd_processes = 1
        #
        ## If there are more than one get_iplayer_downloader processes running,
        ## then don't perform the 'running in parallel' check (self.processes is
        ## the number of >all< the get_iplayer processes on the system).
        ## (TODO Limit self.processes to the get_iplayer processes which belong
        ## to the current get_iplayer_downloader process).
        ## TODO detect 'programme type' change
        ##PVR_CHECK_BUTTON
        ##if gipd_processes == 1 and not dry_run and not pvr_queue:
        #if gipd_processes == 1 and not dry_run:
        #    # Update self.processes now, to avoid any progress bar update delay
        #    self._update_processes_count()
        #    if self.processes > 0:
        #        #if not force:
        #        # Remove already downloaded PIDs from the PID set (copy of pid_list)
        #        pid_set = set(pid_list)
        #        downloaded_pid_set = set(self.downloaded_pid_list)
        #        pid_list = list(pid_set.difference(downloaded_pid_set))
        #    if len(pid_list) == 0:
        #        dialog = Gtk.MessageDialog(self.main_window, 0,
        #                                   Gtk.MessageType.INFO, Gtk.ButtonsType.CLOSE,
        #                                   "Already downloading all the selected episodes")
        #        #dialog.format_secondary_text("")
        #        dialog.run()
        #        dialog.destroy()
        #        #return True
        #        return

        ####

        self.main_window.display_busy_mouse_cursor(True)
        launched, process_output = get_iplayer.get(
            pid_list,
            pid=True,
            pvr_queue=pvr_queue,
            preset=preset,
            prog_type=prog_type,
            alt_recording_mode=alt_recording_mode,
            dry_run=dry_run,
            force=force,
            future=future)
        self.main_window.display_busy_mouse_cursor(False)

        self.on_progress_bar_update(None)

        if not launched:
            dialog = Gtk.MessageDialog(self.main_window, 0,
                                       Gtk.MessageType.INFO,
                                       Gtk.ButtonsType.CLOSE,
                                       "get_iplayer not launched")
            #dialog.format_secondary_text("")
            dialog.run()
            dialog.destroy()
        #PVR_CHECK_BUTTON
        #elif pvr_queue_checkbox_state or pvr_queue or future:
        #    # If implicitly or explicitly queuing, always show the Queued Episodes dialog window,
        #    # even if nothing will be queued
        elif dry_run or pvr_queue or future:
            if dry_run:
                if process_output is not None:
                    process_output = process_output.replace("; ", "\n") + "\n"
                message_format = "Command list"
            else:
                message_format = "Queued Episodes"

            # When queuing episodes, always show the Queued Episodes dialog window, even if nothing will be queued
            dialog = ExtendedMessageDialog(self.main_window, 0,
                                           Gtk.MessageType.INFO,
                                           Gtk.ButtonsType.CLOSE,
                                           message_format)
            #dialog.format_secondary_text("")
            dialog.set_default_response(Gtk.ResponseType.CLOSE)
            dialog.get_content_area().set_size_request(
                get_iplayer_downloader.ui.main_window.WINDOW_LARGE_WIDTH,
                get_iplayer_downloader.ui.main_window.WINDOW_LARGE_HEIGHT)
            dialog.format_tertiary_scrolled_text(
                "" if process_output is None else process_output)
            label = dialog.get_scrolled_label()
            label.set_valign(Gtk.Align.START)
            label.set_halign(Gtk.Align.START)
            label.set_selectable(True)
            #label.override_font(Pango.FontDescription("monospace small"))
            label.override_font(Pango.FontDescription("monospace 10"))
            dialog.run()
            dialog.destroy()
        else:
            #self.main_window.main_tree_view.grab_focus()
            self.downloaded_pid_list = pid_list
class LogViewerDialogWrapper(object):
    def __init__(self, main_controller):
        self.main_controller = main_controller

        self._init_dialog()

    def _init_dialog(self):
        self.dialog = ExtendedMessageDialog(
            self.main_controller.main_window,
            0,
            Gtk.MessageType.INFO,
            Gtk.ButtonsType.NONE,
            "",
            title="download log - " + get_iplayer_downloader.PROGRAM_NAME,
        )

        label = self.dialog.get_scrolled_label()
        label.set_valign(Gtk.Align.START)
        label.set_halign(Gtk.Align.START)
        label.set_selectable(True)

        # label.override_font(Pango.FontDescription("monospace small"))
        label.override_font(Pango.FontDescription("monospace 10"))
        # ALTERNATIVE
        # css_provider = Gtk.CssProvider()
        # css_provider.load_from_data(b""" * { font: monospace; font-size: 10; } """)
        # context = label.get_style_context()
        # context.add_provider(css_provider, Gtk.STYLE_PROVIDER_PRIORITY_USER)

        self.dialog.add_button("Clear log and cache", CLEAR_CACHE_BUTTON_ID)
        self.dialog.add_button("Reset error count", RESET_ERROR_COUNT_BUTTON_ID)
        self.dialog.add_button("Detailed log", FULL_LOG_BUTTON_ID)
        self.dialog.add_button("Log", SUMMARY_LOG_BUTTON_ID)
        self.dialog.add_button(Gtk.STOCK_CLOSE, Gtk.ResponseType.CLOSE)

        # Dialog buttons are laid out from left to right
        button = self.dialog.get_action_area().get_children()[0]
        # button.set_image(Gtk.Image(icon_name=Gtk.STOCK_DELETE))
        button.set_tooltip_text("Remove log files and image cache files")
        button = self.dialog.get_action_area().get_children()[1]
        # button.set_image(Gtk.Image(icon_name=Gtk.STOCK_CLEAR))
        button.set_tooltip_text("Reset error count in the progress bar")
        button = self.dialog.get_action_area().get_children()[2]
        button.set_image(Gtk.Image(icon_name=Gtk.STOCK_REFRESH))
        button.set_tooltip_text("Refresh today's download log")
        button = self.dialog.get_action_area().get_children()[3]
        button.set_image(Gtk.Image(icon_name=Gtk.STOCK_REFRESH))
        button.set_tooltip_text("Refresh today's summary download log")
        # button.grab_focus()
        # Close button
        button = self.dialog.get_action_area().get_children()[4]
        button.grab_focus()

        self.dialog.set_default_response(Gtk.ResponseType.CLOSE)
        # self.dialog.format_secondary_text("")
        self.dialog.get_content_area().set_size_request(
            get_iplayer_downloader.ui.main_window.WINDOW_LARGE_WIDTH,
            get_iplayer_downloader.ui.main_window.WINDOW_LARGE_HEIGHT,
        )

    def run(self):
        button_id_prev = Gtk.ResponseType.CLOSE
        button_id = SUMMARY_LOG_BUTTON_ID
        full = False
        while True:
            if button_id == FULL_LOG_BUTTON_ID or button_id == SUMMARY_LOG_BUTTON_ID:
                full = button_id == FULL_LOG_BUTTON_ID
            if full:
                message_format = "Detailed Download Log"
            else:
                message_format = "Download Log"
            markup = not full
            log_output = command_util.download_log(full=full, markup=markup)

            # Set dialog content title
            self.dialog.set_property("text", message_format)
            # Set dialog content text
            # NOTE If full download log text is too large, it won't be displayed
            if markup:
                self.dialog.format_tertiary_scrolled_markup(log_output)
            else:
                self.dialog.format_tertiary_scrolled_text(log_output)

            # Grab focus to enable immediate page-up/page-down scrolling with the keyboard
            # label = self.dialog.get_scrolled_label()
            # scrolled_window = label.get_parent().get_parent()
            # scrolled_window.grab_focus()

            if button_id == FULL_LOG_BUTTON_ID or button_id == SUMMARY_LOG_BUTTON_ID:
                if button_id_prev != button_id:
                    # Log view changed (different log view type or log files removed)
                    # Scroll to top
                    label = self.dialog.get_scrolled_label()
                    adjustment = label.get_parent().get_vadjustment()
                    adjustment.set_value(0.0)
                    adjustment.value_changed()
                    # adjustment = label.get_parent().set_vadjustment(adjustment)

            if button_id != RESET_ERROR_COUNT_BUTTON_ID:
                # No need to track RESET_ERROR_COUNT_BUTTON_ID because it doesn't affect the log view
                button_id_prev = button_id

            button_id = self.dialog.run()

            if button_id == CLEAR_CACHE_BUTTON_ID:
                command_util.clear_cache()
                self.main_controller.on_progress_bar_update(None)
            elif button_id == RESET_ERROR_COUNT_BUTTON_ID:
                self.main_controller.invalidate_error_offset()
            elif button_id == Gtk.ResponseType.CLOSE or button_id == Gtk.ResponseType.DELETE_EVENT:
                break

    def destroy(self):
        # if self.dialog is not None:
        self.dialog.destroy()
    def on_button_similar_clicked(self, button, locate_search_term):
        # button can be None
        
        if os.name == "posix" and locate_search_term is not None:
            output = ""

            self.main_window.display_busy_mouse_cursor(True)
            cmd = "locate " + file.sanitize_path(locate_search_term, False)
            process_output = command.run(cmd, quiet=True)
            output += cmd + ":\n" + process_output
            self.main_window.display_busy_mouse_cursor(False)
                             
            dialog = ExtendedMessageDialog(self.main_window, 0, Gtk.MessageType.INFO, Gtk.ButtonsType.CLOSE,
                                           "Similar Episodes")
            #dialog.format_secondary_text("")
            dialog.set_default_response(Gtk.ResponseType.CLOSE)
            dialog.get_content_area().set_size_request(get_iplayer_downloader.ui.main_window.WINDOW_LARGE_WIDTH_WIDE,
                                                       get_iplayer_downloader.ui.main_window.WINDOW_LARGE_HEIGHT_WIDE)

            dialog.format_tertiary_scrolled_text(output)
            label = dialog.get_scrolled_label()
            label.set_valign(Gtk.Align.START)
            label.set_halign(Gtk.Align.START)
            label.set_selectable(True)
            #label.override_font(Pango.FontDescription("monospace small"))
            label.override_font(Pango.FontDescription("monospace 10"))
            dialog.run()
            dialog.destroy()
    def on_button_download_clicked(self, button, pvr_queue=False):
        # button can be None
        preset = None
        prog_type = None
        alt_recording_mode = False
        combo = self.tool_bar_box.preset_combo
        tree_iter = combo.get_active_iter()
        if tree_iter is not None:
            model = combo.get_model()
            
            #NOTE Downloading in "All" preset mode will not even work when all settings (radiomode, tvmode,
            #     outputradio, outputtv, etc.) are in one file (in the options file or a single preset file).
            #     Get_iplayer.get() cannot (easily) determine the prog_type of each episode and
            #     get_iplayer does not determine the programme type by it self
            #search_all = self.tool_bar_box.search_all_presets_check_button.get_active()
            #if search_all_presets:
            if string.str2bool(settings.get_config().get(config.NOSECTION, "disable-presets")):
                preset = None
            else:
                preset = model[tree_iter][self.PresetComboModelColumn.PRESET]
            prog_type = model[tree_iter][self.PresetComboModelColumn.PROG_TYPE]
            #channel = model[tree_iter][self.PresetComboModelColumn.CHANNEL]

            alt_recording_mode = self.tool_bar_box.alt_recording_mode_check_button.get_active()
    
        dry_run = self.tool_bar_box.dry_run_check_button.get_active()
        force = self.tool_bar_box.force_check_button.get_active()
        future = self.tool_bar_box.future_check_button.get_active()
        
        #PVR_CHECK_BUTTON
        #pvr_queue_checkbox_state = self.tool_bar_box.pvr_queue_check_button.get_active()
        #if button is not None and not pvr_queue:
        #    # If event was raised from the tool bar download button and not from a keyboard shortcut,
        #    # then the PVR check button determines the download/queue mode
        #    pvr_queue = pvr_queue_checkbox_state
        
        # Search selected leaf nodes (the second level) two levels deep
        model = self.main_tree_view.get_model()
        #indices = ""
        pid_list = []
        root_iter = model.get_iter_first()
        while root_iter is not None:
            row = model[root_iter]
            if row[SearchResultColumn.DOWNLOAD] and row[SearchResultColumn.PID]:
                #indices += row[SearchResultColumn.INDEX] + " "
                pid_list.append(row[SearchResultColumn.PID])
            
            #if model.iter_has_child(root_iter):
            child_iter = model.iter_children(root_iter)
            while child_iter is not None:
                row = model[child_iter]
                if row[SearchResultColumn.DOWNLOAD]:
                    #indices += row[SearchResultColumn.INDEX] + " "
                    pid_list.append(row[SearchResultColumn.PID])
                child_iter = model.iter_next(child_iter)
            root_iter = model.iter_next(root_iter)

        #if not indices:
        if len(pid_list) == 0:
            dialog = Gtk.MessageDialog(self.main_window, 0,
                                       Gtk.MessageType.INFO, Gtk.ButtonsType.CLOSE,
                                       "No episodes selected")
            #dialog.format_secondary_text("")
            dialog.run()
            dialog.destroy()
            #return True
            return
        
        ####
        
        ## Avoid downloading an episode twice in parallel, otherwise continue downloading
        ## twice and let get_iplayer generate an "Already in history" INFO log message.
        ## The user can download episodes in parallel without having
        ## to clear the previous download selection and therefore avoiding
        ## download errors because of two threads trying to download the same episode
        #
        #try:
        #    if os.name == "posix":
        #        #NOTE 2>/dev/null: to surpress error messages, e.g.:
        #        #    Signal 18 (CONT) caught by ps (procps-ng version 3.3.9).
        #        #    ps:display.c:66: please report this bug
        #        gipd_processes = int(command.run("echo -n $(ps xo cmd 2>/dev/null | grep 'get_iplayer_downloader' | grep 'python' | grep -v 'grep' | wc -l) ; exit 0", quiet=True))
        #    else:
        #        gipd_processes = 1
        #except ValueError:
        #    # Sometimes gipd_processes is not a valid int (empty string?)
        #    gipd_processes = 1
        #
        ## If there are more than one get_iplayer_downloader processes running,
        ## then don't perform the 'running in parallel' check (self.processes is
        ## the number of >all< the get_iplayer processes on the system).
        ## (TODO Limit self.processes to the get_iplayer processes which belong
        ## to the current get_iplayer_downloader process).
        ## TODO detect 'programme type' change
        ##PVR_CHECK_BUTTON
        ##if gipd_processes == 1 and not dry_run and not pvr_queue:
        #if gipd_processes == 1 and not dry_run:
        #    # Update self.processes now, to avoid any progress bar update delay
        #    self._update_processes_count()
        #    if self.processes > 0:
        #        #if not force:
        #        # Remove already downloaded PIDs from the PID set (copy of pid_list)
        #        pid_set = set(pid_list)
        #        downloaded_pid_set = set(self.downloaded_pid_list)
        #        pid_list = list(pid_set.difference(downloaded_pid_set))
        #    if len(pid_list) == 0:
        #        dialog = Gtk.MessageDialog(self.main_window, 0,
        #                                   Gtk.MessageType.INFO, Gtk.ButtonsType.CLOSE,
        #                                   "Already downloading all the selected episodes")
        #        #dialog.format_secondary_text("")
        #        dialog.run()
        #        dialog.destroy()
        #        #return True
        #        return        
        
        ####
        
        self.main_window.display_busy_mouse_cursor(True)
        launched, process_output = get_iplayer.get(pid_list, pid=True, pvr_queue=pvr_queue, preset=preset,
                                                   prog_type=prog_type, alt_recording_mode=alt_recording_mode,
                                                   dry_run=dry_run, force=force, future=future)
        self.main_window.display_busy_mouse_cursor(False)
        
        self.on_progress_bar_update(None)

        if not launched:
            dialog = Gtk.MessageDialog(self.main_window, 0,
                                       Gtk.MessageType.INFO, Gtk.ButtonsType.CLOSE,
                                       "get_iplayer not launched")
            #dialog.format_secondary_text("")
            dialog.run()
            dialog.destroy()
        #PVR_CHECK_BUTTON
        #elif pvr_queue_checkbox_state or pvr_queue or future:
        #    # If implicitly or explicitly queuing, always show the Queued Episodes dialog window,
        #    # even if nothing will be queued
        elif dry_run or pvr_queue or future:
            if dry_run:
                if process_output is not None:
                    process_output = process_output.replace("; ", "\n") + "\n"
                message_format = "Command list"
            else:
                message_format = "Queued Episodes"

            # When queuing episodes, always show the Queued Episodes dialog window, even if nothing will be queued
            dialog = ExtendedMessageDialog(self.main_window, 0, Gtk.MessageType.INFO, Gtk.ButtonsType.CLOSE, message_format)
            #dialog.format_secondary_text("")
            dialog.set_default_response(Gtk.ResponseType.CLOSE)
            dialog.get_content_area().set_size_request(get_iplayer_downloader.ui.main_window.WINDOW_LARGE_WIDTH,
                                                       get_iplayer_downloader.ui.main_window.WINDOW_LARGE_HEIGHT)
            dialog.format_tertiary_scrolled_text("" if process_output is None else process_output)
            label = dialog.get_scrolled_label()
            label.set_valign(Gtk.Align.START)
            label.set_halign(Gtk.Align.START)
            label.set_selectable(True)
            #label.override_font(Pango.FontDescription("monospace small"))
            label.override_font(Pango.FontDescription("monospace 10"))
            dialog.run()
            dialog.destroy()
        else:
            #self.main_window.main_tree_view.grab_focus()
            self.downloaded_pid_list = pid_list