Merge branch 'master' into dev-adaptive

This commit is contained in:
Teemu Ikonen 2022-02-04 12:30:02 +02:00
commit cab3629813
59 changed files with 21678 additions and 17282 deletions

View File

@ -3,7 +3,7 @@ version: 2
jobs:
release-from-macos:
macos:
xcode: "11.1.0"
xcode: "11.4.1"
shell: /bin/bash --login -o pipefail
environment:
- BUNDLE_TAG: 21.4.27

1034
po/ca.po

File diff suppressed because it is too large Load Diff

1062
po/cs.po

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

1078
po/da.po

File diff suppressed because it is too large Load Diff

1081
po/de.po

File diff suppressed because it is too large Load Diff

1079
po/el.po

File diff suppressed because it is too large Load Diff

1078
po/es.po

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

1085
po/eu.po

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

1078
po/fi.po

File diff suppressed because it is too large Load Diff

1077
po/fr.po

File diff suppressed because it is too large Load Diff

1079
po/gl.po

File diff suppressed because it is too large Load Diff

1079
po/he.po

File diff suppressed because it is too large Load Diff

1186
po/hu.po

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

1077
po/it.po

File diff suppressed because it is too large Load Diff

1078
po/kk.po

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

2073
po/nb.po

File diff suppressed because it is too large Load Diff

1078
po/nl.po

File diff suppressed because it is too large Load Diff

1077
po/nn.po

File diff suppressed because it is too large Load Diff

1188
po/pl.po

File diff suppressed because it is too large Load Diff

1073
po/pt.po

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

1063
po/ro.po

File diff suppressed because it is too large Load Diff

1078
po/ru.po

File diff suppressed because it is too large Load Diff

1071
po/sk.po

File diff suppressed because it is too large Load Diff

1078
po/sv.po

File diff suppressed because it is too large Load Diff

1081
po/tr.po

File diff suppressed because it is too large Load Diff

1079
po/uk.po

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -38,8 +38,8 @@ class gPodderExtension:
dlg = Gtk.FileChooserDialog(title=_('Save video'),
parent=self.gpodder.get_dialog_parent(),
action=Gtk.FileChooserAction.SAVE)
dlg.add_button(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL)
dlg.add_button(Gtk.STOCK_SAVE, Gtk.ResponseType.OK)
dlg.add_button(_('_Cancel'), Gtk.ResponseType.CANCEL)
dlg.add_button(_('_Save'), Gtk.ResponseType.OK)
if dlg.run() == Gtk.ResponseType.OK:
filename = dlg.get_filename()

View File

@ -20,6 +20,7 @@
# Windows 7 taskbar progress
# Sean Munkel; 2013-01-05
import ctypes
import functools
import logging
from ctypes import (HRESULT, POINTER, Structure, alignment, c_int, c_uint,
@ -161,7 +162,11 @@ class gPodderExtension:
def on_ui_object_available(self, name, ui_object):
def callback(self, window, *args):
self.window_handle = window.window.handle
ctypes.pythonapi.PyCapsule_GetPointer.restype = ctypes.c_void_p
ctypes.pythonapi.PyCapsule_GetPointer.argtypes = [ctypes.py_object]
win_gpointer = ctypes.pythonapi.PyCapsule_GetPointer(window.get_window().__gpointer__, None)
gdkdll = ctypes.CDLL("libgdk-3-0.dll")
self.window_handle = gdkdll.gdk_win32_window_get_handle(win_gpointer)
if name == 'gpodder-gtk':
ui_object.main_window.connect('realize',

View File

@ -86,14 +86,10 @@ class YoutubeCustomDownload(download.CustomDownload):
"""
self._reporthook = reporthook
# outtmpl: use given tempname by DownloadTask
# (escape % and $ because outtmpl used as a string template by youtube-dl)
outtmpl = tempname.replace('%', '%%').replace('$', '$$')
# (escape % because outtmpl used as a string template by youtube-dl)
outtmpl = tempname.replace('%', '%%')
res = self._ytdl.fetch_video(self._url, outtmpl, self._my_hook)
if outtmpl != tempname:
if 'ext' in res and os.path.isfile(outtmpl + '.{}'.format(res['ext'])):
os.rename(outtmpl + '.{}'.format(res['ext']), tempname)
else:
os.rename(outtmpl, tempname)
# Renaming is not required because the escaped percent is not escaped in the output file.
if 'duration' in res and res['duration']:
self._episode.total_time = res['duration']
headers = {}
@ -468,7 +464,8 @@ class gPodderExtension:
def on_episodes_context_menu(self, episodes):
if not self.container.config.manage_downloads \
and not all(e.was_downloaded(and_exists=True) for e in episodes):
and not all(e.was_downloaded(and_exists=True) for e in episodes) \
and not any(e.downloading for e in episodes):
return [(_("Download with Youtube-DL"), self.download_episodes)]
def download_episodes(self, episodes):

View File

@ -1,54 +1,43 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.38.2 -->
<!--*- mode: xml -*-->
<interface>
<!-- interface-requires gtk+ 3.10 -->
<requires lib="gtk+" version="3.16"/>
<object class="GtkAdjustment" id="adjustment1">
<property name="upper">10240</property>
<property name="lower">0.5</property>
<property name="page_increment">0</property>
<property name="step_increment">0.5</property>
<property name="page_size">0</property>
<property name="upper">10240</property>
<property name="step-increment">0.5</property>
</object>
<object class="GtkAdjustment" id="adjustment2">
<property name="upper">16</property>
<property name="lower">1</property>
<property name="page_increment">0</property>
<property name="step_increment">1</property>
<property name="page_size">0</property>
<property name="upper">16</property>
<property name="step-increment">1</property>
</object>
<object class="GtkApplicationWindow" id="gPodder">
<property name="name">gPodder</property>
<property name="application">app</property>
<property name="visible">False</property>
<property name="can-focus">False</property>
<property name="title">gPodder</property>
<property name="window_position">GTK_WIN_POS_CENTER</property>
<property name="modal">False</property>
<property name="destroy_with_parent">False</property>
<property name="skip_taskbar_hint">False</property>
<property name="skip_pager_hint">False</property>
<property name="type_hint">GDK_WINDOW_TYPE_HINT_NORMAL</property>
<property name="focus_on_map">True</property>
<property name="urgency_hint">False</property>
<signal handler="on_gPodder_delete_event" name="delete-event"/>
<property name="window-position">center</property>
<signal name="delete-event" handler="on_gPodder_delete_event" swapped="no"/>
<child>
<!-- n-columns=1 n-rows=2 -->
<object class="GtkGrid" id="vMain">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkToolbar" id="toolbar">
<property name="visible">True</property>
<property name="show_arrow">True</property>
<property name="can-focus">False</property>
<child>
<object class="GtkToolButton" id="toolDownload">
<property name="visible">True</property>
<property name="label" translatable="yes">Download</property>
<property name="use_underline">True</property>
<property name="stock_id">gtk-go-down</property>
<property name="visible_horizontal">True</property>
<property name="visible_vertical">True</property>
<property name="is_important">True</property>
<property name="sensitive">False</property>
<signal handler="on_download_selected_episodes" name="clicked"/>
<property name="can-focus">False</property>
<property name="is-important">True</property>
<property name="label" translatable="yes">Download</property>
<property name="icon-name">go-down</property>
<signal name="clicked" handler="on_download_selected_episodes" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
@ -58,12 +47,12 @@
<child>
<object class="GtkToolButton" id="toolPlay">
<property name="visible">True</property>
<property name="stock_id">gtk-media-play</property>
<property name="visible_horizontal">True</property>
<property name="visible_vertical">True</property>
<property name="is_important">True</property>
<property name="sensitive">False</property>
<signal handler="on_playback_selected_episodes" name="clicked"/>
<property name="can-focus">False</property>
<property name="is-important">True</property>
<property name="label" translatable="yes">Play</property>
<property name="icon-name">media-playback-start</property>
<signal name="clicked" handler="on_playback_selected_episodes" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
@ -73,14 +62,12 @@
<child>
<object class="GtkToolButton" id="toolCancel">
<property name="visible">True</property>
<property name="label" translatable="yes">Cancel</property>
<property name="use_underline">True</property>
<property name="stock_id">gtk-cancel</property>
<property name="visible_horizontal">True</property>
<property name="visible_vertical">True</property>
<property name="is_important">True</property>
<property name="sensitive">False</property>
<signal handler="on_item_cancel_download_activate" name="clicked"/>
<property name="can-focus">False</property>
<property name="is-important">True</property>
<property name="label" translatable="yes">Cancel</property>
<property name="icon-name">process-stop</property>
<signal name="clicked" handler="on_item_cancel_download_activate" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
@ -90,8 +77,7 @@
<child>
<object class="GtkSeparatorToolItem" id="toolbutton3">
<property name="visible">True</property>
<property name="visible_horizontal">True</property>
<property name="visible_vertical">True</property>
<property name="can-focus">False</property>
</object>
<packing>
<property name="expand">False</property>
@ -101,12 +87,10 @@
<child>
<object class="GtkToolButton" id="toolPreferences">
<property name="visible">True</property>
<property name="stock_id">gtk-preferences</property>
<property name="visible_horizontal">True</property>
<property name="visible_vertical">True</property>
<property name="is_important">False</property>
<property name="can-focus">False</property>
<property name="action-name">app.preferences</property>
<property name="label" translatable="yes">Preferences</property>
<property name="icon-name">preferences-desktop</property>
</object>
<packing>
<property name="expand">False</property>
@ -116,8 +100,7 @@
<child>
<object class="GtkSeparatorToolItem" id="toolbutton2">
<property name="visible">True</property>
<property name="visible_horizontal">True</property>
<property name="visible_vertical">True</property>
<property name="can-focus">False</property>
</object>
<packing>
<property name="expand">False</property>
@ -127,12 +110,10 @@
<child>
<object class="GtkToolButton" id="toolQuit">
<property name="visible">True</property>
<property name="stock_id">gtk-quit</property>
<property name="visible_horizontal">True</property>
<property name="visible_vertical">True</property>
<property name="is_important">False</property>
<signal handler="on_gPodder_delete_event" name="clicked"/>
<property name="can-focus">False</property>
<property name="label" translatable="yes">Quit</property>
<property name="icon-name">application-exit</property>
<signal name="clicked" handler="on_gPodder_delete_event" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
@ -140,348 +121,430 @@
</packing>
</child>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">0</property>
</packing>
</child>
<child>
<!-- n-columns=1 n-rows=1 -->
<object class="GtkGrid" id="hboxContainer">
<property name="border_width">5</property>
<property name="visible">True</property>
<property name="orientation">horizontal</property>
<property name="can-focus">False</property>
<property name="hexpand">True</property>
<property name="vexpand">True</property>
<property name="border-width">5</property>
<child>
<object class="GtkNotebook" id="wNotebook">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="show_tabs">True</property>
<property name="show_border">True</property>
<property name="tab_pos">GTK_POS_TOP</property>
<property name="scrollable">False</property>
<property name="enable_popup">False</property>
<signal handler="on_wNotebook_switch_page" name="switch_page"/>
<property name="can-focus">True</property>
<signal name="switch-page" handler="on_wNotebook_switch_page" swapped="no"/>
<child>
<object class="GtkPaned" id="channelPaned">
<property name="border_width">5</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="orientation">horizontal</property>
<property name="can-focus">True</property>
<property name="border-width">5</property>
<child>
<!-- n-columns=1 n-rows=3 -->
<object class="GtkGrid" id="vboxChannelNavigator">
<property name="visible">True</property>
<property name="row_spacing">5</property>
<property name="can-focus">False</property>
<property name="orientation">vertical</property>
<property name="row-spacing">5</property>
<child>
<object class="GtkScrolledWindow" id="scrolledwindow6">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
<property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
<property name="vexpand">True</property>
<property name="shadow_type">GTK_SHADOW_IN</property>
<property name="window_placement">GTK_CORNER_TOP_LEFT</property>
<property name="can-focus">True</property>
<property name="vexpand">True</property>
<property name="shadow-type">in</property>
<child>
<object class="GtkTreeView" id="treeChannels">
<property name="name">treeChannels</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="headers_visible">False</property>
<property name="rules_hint">False</property>
<property name="can-focus">True</property>
<property name="has-tooltip">True</property>
<property name="reorderable">False</property>
<property name="enable_search">True</property>
<property name="fixed_height_mode">False</property>
<property name="hover_selection">False</property>
<property name="hover_expand">False</property>
<signal handler="on_treeChannels_row_activated" name="row_activated"/>
<signal handler="on_treeChannels_cursor_changed" name="cursor_changed"/>
<signal handler="on_treeview_query_tooltip" name="query-tooltip"/>
<signal handler="on_treeview_expose_event" name="draw"/>
<signal handler="on_treeview_button_pressed" name="button-press-event"/>
<signal handler="on_treeview_podcasts_button_released" name="button-release-event"/>
<property name="headers-visible">False</property>
<signal name="button-press-event" handler="on_treeview_button_pressed" swapped="no"/>
<signal name="button-release-event" handler="on_treeview_podcasts_button_released" swapped="no"/>
<signal name="cursor-changed" handler="on_treeChannels_cursor_changed" swapped="no"/>
<signal name="draw" handler="on_treeview_expose_event" swapped="no"/>
<signal name="query-tooltip" handler="on_treeview_query_tooltip" swapped="no"/>
<signal name="row-activated" handler="on_treeChannels_row_activated" swapped="no"/>
<child internal-child="selection">
<object class="GtkTreeSelection"/>
</child>
</object>
</child>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">0</property>
</packing>
</child>
<child>
<!-- n-columns=1 n-rows=1 -->
<object class="GtkGrid" id="hbox_search_podcasts">
<property name="column_spacing">6</property>
<property name="orientation">horizontal</property>
<property name="can-focus">False</property>
<property name="column-spacing">6</property>
<child>
<object class="GtkEntry" id="entry_search_podcasts">
<property name="hexpand">True</property>
<property name="visible">True</property>
<property name="secondary-icon-stock">gtk-close</property>
<property name="can-focus">True</property>
<property name="hexpand">True</property>
<property name="secondary-icon-name">edit-clear</property>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">0</property>
</packing>
</child>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">1</property>
</packing>
</child>
<child>
<!-- n-columns=1 n-rows=2 -->
<object class="GtkGrid" id="vbox42">
<property name="visible">True</property>
<property name="orientation">vertical</property>
<property name="can-focus">False</property>
<property name="hexpand">True</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkButton" id="btnUpdateFeeds">
<property name="label" translatable="yes">Check for new episodes</property>
<property name="can_focus">True</property>
<property name="focus_on_click">True</property>
<property name="action-name">win.update</property>
<property name="can-focus">True</property>
<property name="receives-default">False</property>
<property name="hexpand">True</property>
<property name="action-name">win.update</property>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">0</property>
</packing>
</child>
<child>
<!-- n-columns=2 n-rows=1 -->
<object class="GtkGrid" id="hboxUpdateFeeds">
<property name="column_spacing">6</property>
<property name="orientation">horizontal</property>
<property name="can-focus">False</property>
<property name="column-spacing">6</property>
<child>
<object class="GtkProgressBar" id="pbFeedUpdate">
<property name="can-focus">False</property>
<property name="hexpand">True</property>
<property name="pulse_step">0.10000000149</property>
<property name="pulse-step">0.10000000149</property>
<property name="show-text">True</property>
<property name="ellipsize">PANGO_ELLIPSIZE_MIDDLE</property>
<property name="ellipsize">middle</property>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="btnCancelFeedUpdate">
<property name="can_focus">True</property>
<property name="focus_on_click">True</property>
<signal handler="on_btnCancelFeedUpdate_clicked" name="clicked"/>
<property name="can-focus">True</property>
<property name="receives-default">False</property>
<signal name="clicked" handler="on_btnCancelFeedUpdate_clicked" swapped="no"/>
<child>
<object class="GtkImage" id="image3209">
<property name="visible">True</property>
<property name="stock">gtk-cancel</property>
<property name="icon_size">4</property>
<property name="can-focus">False</property>
<property name="icon-name">process-stop</property>
</object>
</child>
</object>
<packing>
<property name="left-attach">1</property>
<property name="top-attach">0</property>
</packing>
</child>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">1</property>
</packing>
</child>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">2</property>
</packing>
</child>
</object>
<packing>
<property name="shrink">False</property>
<property name="resize">False</property>
<property name="shrink">False</property>
</packing>
</child>
<child>
<!-- n-columns=1 n-rows=2 -->
<object class="GtkGrid" id="vbox_episode_list">
<property name="visible">True</property>
<property name="row_spacing">6</property>
<property name="can-focus">False</property>
<property name="orientation">vertical</property>
<property name="row-spacing">6</property>
<child>
<object class="GtkScrolledWindow" id="scrollAvailable">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
<property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
<property name="shadow_type">GTK_SHADOW_IN</property>
<property name="hexpand">True</property>
<property name="vexpand">True</property>
<property name="window_placement">GTK_CORNER_TOP_LEFT</property>
<property name="can-focus">True</property>
<property name="hexpand">True</property>
<property name="vexpand">True</property>
<property name="shadow-type">in</property>
<child>
<object class="GtkTreeView" id="treeAvailable">
<property name="name">treeAvailable</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="headers_visible">True</property>
<property name="rules_hint">True</property>
<property name="can-focus">True</property>
<property name="has-tooltip">True</property>
<property name="enable-search">False</property>
<property name="rubber-banding">True</property>
<property name="reorderable">False</property>
<property name="enable_search">False</property>
<property name="fixed_height_mode">False</property>
<property name="hover_selection">False</property>
<property name="hover_expand">False</property>
<signal handler="on_treeAvailable_row_activated" name="row_activated"/>
<signal handler="on_treeview_query_tooltip" name="query-tooltip"/>
<signal handler="on_treeview_expose_event" name="draw"/>
<signal handler="on_treeview_button_pressed" name="button-press-event"/>
<signal handler="on_treeview_episodes_button_released" name="button-release-event"/>
<signal name="button-press-event" handler="on_treeview_button_pressed" swapped="no"/>
<signal name="button-release-event" handler="on_treeview_episodes_button_released" swapped="no"/>
<signal name="draw" handler="on_treeview_expose_event" swapped="no"/>
<signal name="query-tooltip" handler="on_treeview_query_tooltip" swapped="no"/>
<signal name="row-activated" handler="on_treeAvailable_row_activated" swapped="no"/>
<child internal-child="selection">
<object class="GtkTreeSelection"/>
</child>
</object>
</child>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">0</property>
</packing>
</child>
<child>
<!-- n-columns=2 n-rows=1 -->
<object class="GtkGrid" id="hbox_search_episodes">
<property name="column_spacing">6</property>
<property name="orientation">horizontal</property>
<property name="can-focus">False</property>
<property name="column-spacing">6</property>
<child>
<object class="GtkLabel" id="label_search_episodes">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">Filter:</property>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">0</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="entry_search_episodes">
<property name="hexpand">True</property>
<property name="visible">True</property>
<property name="secondary-icon-stock">gtk-close</property>
<property name="can-focus">True</property>
<property name="hexpand">True</property>
<property name="secondary-icon-name">edit-clear</property>
</object>
<packing>
<property name="left-attach">1</property>
<property name="top-attach">0</property>
</packing>
</child>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">1</property>
</packing>
</child>
</object>
<packing>
<property name="resize">True</property>
<property name="shrink">False</property>
</packing>
</child>
</object>
</child>
<child type="tab">
<object class="GtkLabel" id="label2">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">Podcasts</property>
</object>
<packing>
<property name="tab-fill">False</property>
</packing>
</child>
<child>
<!-- n-columns=1 n-rows=2 -->
<object class="GtkGrid" id="vboxDownloadStatusWidgets">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="border-width">5</property>
<property name="orientation">vertical</property>
<property name="row-spacing">5</property>
<child>
<object class="GtkScrolledWindow" id="scrolledwindow1">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="vexpand">True</property>
<property name="shadow-type">in</property>
<child>
<object class="GtkTreeView" id="treeDownloads">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="headers-visible">False</property>
<property name="reorderable">True</property>
<property name="rubber-banding">True</property>
<signal name="button-press-event" handler="on_treeview_button_pressed" swapped="no"/>
<signal name="button-release-event" handler="on_treeview_downloads_button_released" swapped="no"/>
<signal name="draw" handler="on_treeview_expose_event" swapped="no"/>
<signal name="row-activated" handler="on_treeDownloads_row_activated" swapped="no"/>
<child internal-child="selection">
<object class="GtkTreeSelection"/>
</child>
</object>
</child>
</object>
<packing>
<property name="shrink">False</property>
<property name="resize">True</property>
<property name="left-attach">0</property>
<property name="top-attach">0</property>
</packing>
</child>
</object>
<packing>
<property name="tab_expand">False</property>
<property name="tab_fill">True</property>
</packing>
</child>
<child type="tab">
<object class="GtkLabel" id="label2">
<property name="visible">True</property>
<property name="label" translatable="yes">Podcasts</property>
<property name="use_underline">False</property>
<property name="use_markup">False</property>
<property name="wrap">False</property>
<property name="selectable">False</property>
<property name="width_chars">-1</property>
<property name="single_line_mode">False</property>
</object>
</child>
<child>
<object class="GtkGrid" id="vboxDownloadStatusWidgets">
<property name="border_width">5</property>
<property name="visible">True</property>
<property name="row_spacing">5</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkScrolledWindow" id="scrolledwindow1">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="vexpand">True</property>
<property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
<property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
<property name="shadow_type">GTK_SHADOW_IN</property>
<property name="window_placement">GTK_CORNER_TOP_LEFT</property>
<child>
<object class="GtkTreeView" id="treeDownloads">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="headers_visible">False</property>
<property name="rules_hint">False</property>
<property name="rubber-banding">True</property>
<property name="reorderable">True</property>
<property name="enable_search">True</property>
<property name="fixed_height_mode">False</property>
<property name="hover_selection">False</property>
<property name="hover_expand">False</property>
<signal handler="on_treeDownloads_row_activated" name="row_activated"/>
<signal handler="on_treeview_expose_event" name="draw"/>
<signal handler="on_treeview_button_pressed" name="button-press-event"/>
<signal handler="on_treeview_downloads_button_released" name="button-release-event"/>
</object>
</child>
</object>
</child>
<child>
<!-- n-columns=3 n-rows=1 -->
<object class="GtkGrid" id="hboxDownloadSettings">
<property name="border_width">5</property>
<property name="visible">True</property>
<property name="column_spacing">10</property>
<property name="orientation">horizontal</property>
<property name="can-focus">False</property>
<property name="border-width">5</property>
<property name="column-spacing">10</property>
<child>
<!-- n-columns=3 n-rows=1 -->
<object class="GtkGrid" id="hboxDownloadLimit">
<property name="visible">True</property>
<property name="column_spacing">5</property>
<property name="orientation">horizontal</property>
<property name="can-focus">False</property>
<property name="column-spacing">5</property>
<child>
<object class="GtkCheckButton" id="cbLimitDownloads">
<property name="label" translatable="yes">Limit rate to</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="draw_indicator">True</property>
<signal name="toggled" handler="on_cbLimitDownloads_toggled"/>
<property name="can-focus">True</property>
<property name="receives-default">False</property>
<property name="draw-indicator">True</property>
<signal name="toggled" handler="on_cbLimitDownloads_toggled" swapped="no"/>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">0</property>
</packing>
</child>
<child>
<object class="GtkSpinButton" id="spinLimitDownloads">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="invisible_char">&#x25CF;</property>
<property name="climb_rate">1</property>
<property name="digits">1</property>
<property name="can-focus">True</property>
<property name="invisible-char">●</property>
<property name="adjustment">adjustment1</property>
<property name="climb-rate">1</property>
<property name="digits">1</property>
</object>
<packing>
<property name="left-attach">1</property>
<property name="top-attach">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="labelLimitRate">
<property name="visible">True</property>
<property name="xalign">0</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">KiB/s</property>
<property name="xalign">0</property>
</object>
<packing>
<property name="left-attach">2</property>
<property name="top-attach">0</property>
</packing>
</child>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="DownloadSettingsSpacer">
<property name="visible">True</property>
<property name="hexpand">True</property>
<property name="can-focus">False</property>
<property name="hexpand">True</property>
</object>
<packing>
<property name="left-attach">1</property>
<property name="top-attach">0</property>
</packing>
</child>
<child>
<!-- n-columns=2 n-rows=1 -->
<object class="GtkGrid" id="hboxDownloadRate">
<property name="visible">True</property>
<property name="column_spacing">5</property>
<property name="orientation">horizontal</property>
<property name="can-focus">False</property>
<property name="column-spacing">5</property>
<child>
<object class="GtkCheckButton" id="cbMaxDownloads">
<property name="label" translatable="yes">Limit downloads to</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="draw_indicator">True</property>
<signal name="toggled" handler="on_cbMaxDownloads_toggled"/>
<property name="can-focus">True</property>
<property name="receives-default">False</property>
<property name="draw-indicator">True</property>
<signal name="toggled" handler="on_cbMaxDownloads_toggled" swapped="no"/>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">0</property>
</packing>
</child>
<child>
<object class="GtkSpinButton" id="spinMaxDownloads">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="invisible_char">&#x25CF;</property>
<property name="climb_rate">1</property>
<property name="can-focus">True</property>
<property name="invisible-char">●</property>
<property name="adjustment">adjustment2</property>
<property name="climb-rate">1</property>
</object>
<packing>
<property name="left-attach">1</property>
<property name="top-attach">0</property>
</packing>
</child>
</object>
<packing>
<property name="left-attach">2</property>
<property name="top-attach">0</property>
</packing>
</child>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">1</property>
</packing>
</child>
</object>
<packing>
<property name="tab_expand">False</property>
<property name="tab_fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child type="tab">
<object class="GtkLabel" id="labelDownloads">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">Progress</property>
<property name="use_underline">False</property>
<property name="use_markup">False</property>
<property name="wrap">False</property>
<property name="selectable">False</property>
<property name="width_chars">-1</property>
<property name="single_line_mode">False</property>
</object>
<packing>
<property name="position">1</property>
<property name="tab-fill">False</property>
</packing>
</child>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">0</property>
</packing>
</child>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">1</property>
</packing>
</child>
</object>

View File

@ -1,294 +1,241 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.38.2 -->
<!--*- mode: xml -*-->
<interface>
<requires lib="gtk+" version="3.16"/>
<object class="GtkDialog" id="gPodderEpisodeSelector">
<property name="visible">False</property>
<property name="can-focus">False</property>
<property name="title" translatable="yes">Select episodes</property>
<property name="window_position">GTK_WIN_POS_CENTER_ON_PARENT</property>
<property name="modal">True</property>
<property name="transient-for">parent_widget</property>
<property name="destroy_with_parent">False</property>
<property name="skip_taskbar_hint">False</property>
<property name="skip_pager_hint">False</property>
<property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property>
<property name="focus_on_map">True</property>
<property name="urgency_hint">False</property>
<property name="window-position">center-on-parent</property>
<property name="type-hint">dialog</property>
<child internal-child="vbox">
<object class="GtkBox" id="vbox10">
<property name="visible">True</property>
<property name="homogeneous">False</property>
<property name="can-focus">False</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkBox" id="vbox_for_episode_selector">
<property name="border_width">5</property>
<child internal-child="action_area">
<object class="GtkButtonBox" id="hbox35">
<property name="visible">True</property>
<property name="spacing">5</property>
<property name="orientation">vertical</property>
<property name="can-focus">False</property>
<child>
<object class="GtkLabel" id="labelInstructions">
<property name="label">additional text</property>
<property name="use_underline">False</property>
<property name="use_markup">False</property>
<property name="wrap">False</property>
<property name="selectable">False</property>
<property name="xalign">0</property>
<property name="width_chars">-1</property>
<property name="single_line_mode">False</property>
<object class="GtkButton" id="btnRemoveAction">
<property name="label" translatable="yes">_Remove</property>
<property name="can-focus">True</property>
<property name="receives-default">False</property>
<property name="use-underline">True</property>
<signal name="clicked" handler="on_remove_action_activate" swapped="no"/>
</object>
<packing>
<property name="padding">0</property>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="btnCancel">
<property name="label" translatable="yes">_Cancel</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">False</property>
<property name="use-underline">True</property>
<signal name="clicked" handler="on_btnCancel_clicked" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkButton" id="btnOK">
<property name="label" translatable="yes">_OK</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="can-default">True</property>
<property name="has-default">True</property>
<property name="receives-default">False</property>
<property name="use-underline">True</property>
<property name="always-show-image">True</property>
<signal name="clicked" handler="on_btnOK_clicked" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">2</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkBox" id="vbox_for_episode_selector">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="border-width">5</property>
<property name="orientation">vertical</property>
<property name="spacing">5</property>
<child>
<object class="GtkLabel" id="labelInstructions">
<property name="can-focus">False</property>
<property name="label">additional text</property>
<property name="xalign">0</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkScrolledWindow" id="scrolledwindow7">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
<property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
<property name="shadow_type">GTK_SHADOW_IN</property>
<property name="window_placement">GTK_CORNER_TOP_LEFT</property>
<property name="can-focus">True</property>
<property name="shadow-type">in</property>
<child>
<object class="GtkTreeView" id="treeviewEpisodes">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="has_focus">True</property>
<property name="headers_visible">False</property>
<property name="rules_hint">False</property>
<property name="reorderable">False</property>
<property name="enable_search">False</property>
<property name="fixed_height_mode">False</property>
<property name="hover_selection">False</property>
<property name="hover_expand">False</property>
<signal name="row_activated" handler="on_row_activated"/>
<property name="can-focus">True</property>
<property name="has-focus">True</property>
<property name="headers-visible">False</property>
<property name="enable-search">False</property>
<signal name="row-activated" handler="on_row_activated" swapped="no"/>
<child internal-child="selection">
<object class="GtkTreeSelection"/>
</child>
</object>
</child>
</object>
<packing>
<property name="padding">0</property>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkBox" id="hboxButtons">
<property name="visible">True</property>
<property name="homogeneous">False</property>
<property name="can-focus">False</property>
<property name="spacing">5</property>
<property name="orientation">horizontal</property>
<child>
<object class="GtkButton" id="btnCheckAll">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="focus_on_click">True</property>
<signal handler="on_btnCheckAll_clicked" name="clicked"/>
<property name="can-focus">True</property>
<property name="receives-default">False</property>
<signal name="clicked" handler="on_btnCheckAll_clicked" swapped="no"/>
<child>
<object class="GtkAlignment" id="alignment22">
<object class="GtkBox" id="hbox34">
<property name="visible">True</property>
<property name="xscale">0</property>
<property name="yscale">0</property>
<property name="top_padding">0</property>
<property name="bottom_padding">0</property>
<property name="left_padding">0</property>
<property name="right_padding">0</property>
<property name="can-focus">False</property>
<property name="spacing">2</property>
<child>
<object class="GtkBox" id="hbox34">
<object class="GtkImage" id="image2636">
<property name="visible">True</property>
<property name="homogeneous">False</property>
<property name="spacing">2</property>
<property name="orientation">horizontal</property>
<child>
<object class="GtkImage" id="image2636">
<property name="visible">True</property>
<property name="stock">gtk-apply</property>
<property name="icon_size">4</property>
</object>
<packing>
<property name="padding">0</property>
<property name="expand">False</property>
<property name="fill">False</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label107">
<property name="visible">True</property>
<property name="label" translatable="yes">Select all</property>
<property name="use_underline">True</property>
<property name="use_markup">False</property>
<property name="wrap">False</property>
<property name="selectable">False</property>
<property name="width_chars">-1</property>
<property name="single_line_mode">False</property>
</object>
<packing>
<property name="padding">0</property>
<property name="expand">False</property>
<property name="fill">False</property>
</packing>
</child>
<property name="can-focus">False</property>
<property name="icon-name">object-select</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label107">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">Select _all</property>
<property name="use-underline">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">1</property>
</packing>
</child>
</object>
</child>
</object>
<packing>
<property name="padding">0</property>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="btnCheckNone">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="focus_on_click">True</property>
<signal handler="on_btnCheckNone_clicked" name="clicked"/>
<property name="can-focus">True</property>
<property name="receives-default">False</property>
<signal name="clicked" handler="on_btnCheckNone_clicked" swapped="no"/>
<child>
<object class="GtkAlignment" id="alignment21">
<object class="GtkBox" id="hbox33">
<property name="visible">True</property>
<property name="xscale">0</property>
<property name="yscale">0</property>
<property name="top_padding">0</property>
<property name="bottom_padding">0</property>
<property name="left_padding">0</property>
<property name="right_padding">0</property>
<property name="can-focus">False</property>
<property name="spacing">2</property>
<child>
<object class="GtkBox" id="hbox33">
<object class="GtkImage" id="image2635">
<property name="visible">True</property>
<property name="homogeneous">False</property>
<property name="spacing">2</property>
<property name="orientation">horizontal</property>
<child>
<object class="GtkImage" id="image2635">
<property name="visible">True</property>
<property name="stock">gtk-revert-to-saved</property>
<property name="icon_size">4</property>
</object>
<packing>
<property name="padding">0</property>
<property name="expand">False</property>
<property name="fill">False</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label106">
<property name="visible">True</property>
<property name="label" translatable="yes">Select none</property>
<property name="use_underline">True</property>
<property name="use_markup">False</property>
<property name="wrap">False</property>
<property name="selectable">False</property>
<property name="width_chars">-1</property>
<property name="single_line_mode">False</property>
</object>
<packing>
<property name="padding">0</property>
<property name="expand">False</property>
<property name="fill">False</property>
</packing>
</child>
<property name="can-focus">False</property>
<property name="icon-name">document-revert</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label106">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">Select _none</property>
<property name="use-underline">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">1</property>
</packing>
</child>
</object>
</child>
</object>
<packing>
<property name="padding">0</property>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="labelTotalSize">
<property name="visible">True</property>
<property name="use_underline">False</property>
<property name="use_markup">False</property>
<property name="justify">GTK_JUSTIFY_RIGHT</property>
<property name="wrap">False</property>
<property name="selectable">False</property>
<property name="can-focus">False</property>
<property name="justify">right</property>
<property name="xalign">1</property>
<property name="width_chars">-1</property>
<property name="single_line_mode">False</property>
</object>
<packing>
<property name="padding">0</property>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
<packing>
<property name="padding">0</property>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
<packing>
<property name="padding">0</property>
<property name="expand">True</property>
<property name="fill">True</property>
</packing>
</child>
<child internal-child="action_area">
<object class="GtkBox" id="hbox35">
<property name="visible">True</property>
<property name="homogeneous">False</property>
<property name="spacing">5</property>
<property name="orientation">horizontal</property>
<child>
<object class="GtkButton" id="btnRemoveAction">
<property name="visible">False</property>
<property name="can_focus">True</property>
<property name="label">Remove</property>
<property name="use_stock">True</property>
<property name="focus_on_click">True</property>
<signal handler="on_remove_action_activate" name="clicked"/>
</object>
<packing>
<property name="padding">0</property>
<property name="expand">False</property>
<property name="fill">False</property>
</packing>
</child>
<child>
<object class="GtkButton" id="btnCancel">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="label">gtk-cancel</property>
<property name="use_stock">True</property>
<property name="focus_on_click">True</property>
<signal handler="on_btnCancel_clicked" name="clicked"/>
</object>
<packing>
<property name="padding">0</property>
<property name="expand">False</property>
<property name="fill">False</property>
</packing>
</child>
<child>
<object class="GtkButton" id="btnOK">
<property name="visible">True</property>
<property name="can_default">True</property>
<property name="has_default">True</property>
<property name="can_focus">True</property>
<property name="label">gtk-ok</property>
<property name="use_stock">True</property>
<property name="focus_on_click">True</property>
<signal handler="on_btnOK_clicked" name="clicked"/>
</object>
<packing>
<property name="padding">0</property>
<property name="expand">False</property>
<property name="fill">False</property>
</packing>
</child>
</object>
<packing>
<property name="padding">0</property>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>

View File

@ -26,7 +26,7 @@ from gpodder.sync import (episode_filename_on_device,
episode_foldername_on_device)
import gi # isort:skip
gi.require_version('Gtk', '3.0') # isort:skip
gi.require_version('Gio', '2.0') # isort:skip
from gi.repository import Gio, GLib # isort:skip
_ = gpodder.gettext

View File

@ -624,10 +624,9 @@ class DownloadTask(object):
with self:
# Cancelling directly is allowed if the task isn't currently downloading
if self.status in (self.QUEUED, self.PAUSED, self.FAILED):
self.status = self.CANCELLED
# Call run, so the partial file gets deleted
self.status = self.CANCELLING
# Call run, so the partial file gets deleted, and task recycled
self.run()
self.recycle()
# Otherwise request cancellation
elif self.status == self.DOWNLOADING:
self.status = self.CANCELLING
@ -954,10 +953,6 @@ class DownloadTask(object):
self.status = DownloadTask.FAILED
self.__episode._download_error = self.error_message
# Delete empty partial files, they prevent streaming after a download failure (live stream)
if util.calculate_size(self.filename) == 0:
util.delete_file(self.tempname)
# cancelled/paused -- update state to mark it as safe to manipulate this task again
elif self.status == DownloadTask.PAUSING:
self.status = DownloadTask.PAUSED

View File

@ -230,7 +230,7 @@ class gPodderApplication(Gtk.Application):
def on_about(self, action, param):
dlg = Gtk.Dialog(_('About gPodder'), self.window.gPodder,
Gtk.DialogFlags.MODAL)
dlg.add_button(Gtk.STOCK_CLOSE, Gtk.ResponseType.OK).show()
dlg.add_button(_('_Close'), Gtk.ResponseType.OK).show()
dlg.set_resizable(True)
bg = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6, margin=16)

View File

@ -50,9 +50,6 @@ class GtkBuilderWidget(object):
if parent is not None:
self.builder.expose_object('parent_widget', parent)
self.builder.set_translation_domain(textdomain)
if hasattr(self, '_builder_expose'):
for (key, value) in list(self._builder_expose.items()):
self.builder.expose_object(key, value)
# print >>sys.stderr, 'Creating new from file', self.__class__.__name__
@ -67,6 +64,9 @@ class GtkBuilderWidget(object):
self.builder.connect_signals(self)
self.set_attributes()
if hasattr(self, '_gtk_properties'):
for ((gobj, prop), val) in self._gtk_properties.items():
getattr(self, gobj).set_property(prop, val)
self.new()

View File

@ -165,6 +165,12 @@ class UIConfig(config.Config):
window.set_position(Gtk.WindowPosition.CENTER_ON_PARENT)
else:
window.move(cfg.x, cfg.y)
# From Gtk docs: most window managers ignore requests for initial window
# positions (instead using a user-defined placement algorithm) and honor
# requests after the window has already been shown.
# Move it a second time after the window has been shown.
# The first move reduces chance of window jumping.
util.idle_add(window.move, cfg.x, cfg.y)
# Ignore events while we're connecting to the window
self.__ignore_window_events = True

View File

@ -112,7 +112,7 @@ class gPodderChannel(BuilderWidget):
def on_button_add_section_clicked(self, widget):
text = self.show_text_edit_dialog(_('Add section'), _('New section:'),
affirmative_text=Gtk.STOCK_ADD)
affirmative_text=_('_Add'))
if text is not None:
for index, (section,) in enumerate(self.section_list):
@ -146,8 +146,8 @@ class gPodderChannel(BuilderWidget):
title=_('Select new podcast cover artwork'),
parent=self.gPodderChannel,
action=Gtk.FileChooserAction.OPEN)
dlg.add_button(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL)
dlg.add_button(Gtk.STOCK_OPEN, Gtk.ResponseType.OK)
dlg.add_button(_('_Cancel'), Gtk.ResponseType.CANCEL)
dlg.add_button(_('_Open'), Gtk.ResponseType.OK)
if dlg.run() == Gtk.ResponseType.OK:
url = dlg.get_uri()

View File

@ -59,12 +59,9 @@ class gPodderEpisodeSelector(BuilderWidget):
- title: (optional) The title of the window + heading
- instructions: (optional) A one-line text describing what the
user should select / what the selection is for
- stock_ok_button: (optional) Will replace the "OK" button with
another GTK+ stock item to be used for the
affirmative button of the dialog (e.g. can
be Gtk.STOCK_DELETE when the episodes to be
selected will be deleted after closing the
dialog)
- ok_button: (optional) Will replace the "OK" button label with this
string (e.g. can be '_Delete' when the episodes to be
selected will be deleted after closing the dialog)
- selection_buttons: (optional) A dictionary with labels as
keys and callbacks as values; for each
key a button will be generated, and when
@ -89,6 +86,7 @@ class gPodderEpisodeSelector(BuilderWidget):
COLUMN_ADDITIONAL = 3
def new(self):
self.gPodderEpisodeSelector.set_transient_for(self.parent_widget)
if hasattr(self, 'title'):
self.gPodderEpisodeSelector.set_title(self.title)
@ -134,13 +132,13 @@ class gPodderEpisodeSelector(BuilderWidget):
self.labelInstructions.set_text(self.instructions)
self.labelInstructions.show_all()
if hasattr(self, 'stock_ok_button'):
if self.stock_ok_button == 'gpodder-download':
if hasattr(self, 'ok_button'):
if self.ok_button == 'gpodder-download':
self.btnOK.set_image(Gtk.Image.new_from_icon_name('go-down', Gtk.IconSize.BUTTON))
self.btnOK.set_label(_('Download'))
self.btnOK.set_label(_('_Download'))
else:
self.btnOK.set_label(self.stock_ok_button)
self.btnOK.set_use_stock(True)
self.btnOK.set_image(None)
self.btnOK.set_label(self.ok_button)
# check/uncheck column
toggle_cell = Gtk.CellRendererToggle()
@ -326,9 +324,9 @@ class gPodderEpisodeSelector(BuilderWidget):
self.btnOK.set_sensitive(count > 0)
self.btnRemoveAction.set_sensitive(count > 0)
if count > 0:
self.btnCancel.set_label(Gtk.STOCK_CANCEL)
self.btnCancel.set_label(_('_Cancel'))
else:
self.btnCancel.set_label(Gtk.STOCK_CLOSE)
self.btnCancel.set_label(_('_Close'))
else:
self.btnOK.set_sensitive(False)
self.btnRemoveAction.set_sensitive(False)

View File

@ -671,8 +671,8 @@ class gPodderPreferences(BuilderWidget):
fs = Gtk.FileChooserDialog(title=_('Select folder for mount point'),
action=Gtk.FileChooserAction.SELECT_FOLDER)
fs.set_local_only(False)
fs.add_button(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL)
fs.add_button(Gtk.STOCK_OPEN, Gtk.ResponseType.OK)
fs.add_button(_('_Cancel'), Gtk.ResponseType.CANCEL)
fs.add_button(_('_Open'), Gtk.ResponseType.OK)
fs.set_uri(self.btn_filesystemMountpoint.get_label() or "")
if fs.run() == Gtk.ResponseType.OK:
@ -689,8 +689,8 @@ class gPodderPreferences(BuilderWidget):
fs = Gtk.FileChooserDialog(title=_('Select folder for playlists'),
action=Gtk.FileChooserAction.SELECT_FOLDER)
fs.set_local_only(False)
fs.add_button(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL)
fs.add_button(Gtk.STOCK_OPEN, Gtk.ResponseType.OK)
fs.add_button(_('_Cancel'), Gtk.ResponseType.CANCEL)
fs.add_button(_('_Open'), Gtk.ResponseType.OK)
device_folder = util.new_gio_file(self._config.device_sync.device_folder)
playlists_folder = device_folder.resolve_relative_path(self._config.device_sync.playlists.folder)

View File

@ -155,7 +155,7 @@ class UserAppsReader(object):
self.apps.append(UserApplication(
_('Default application'), 'default',
';'.join((mime + '/*' for mime in self.mimetypes)),
Gtk.STOCK_OPEN))
'document-open'))
def add_separator(self):
self.apps.append(UserApplication(

View File

@ -65,7 +65,7 @@ class DownloadStatusModel(Gtk.ListStore):
# Set up stock icon IDs for tasks
self._status_ids = collections.defaultdict(lambda: None)
self._status_ids[download.DownloadTask.DOWNLOADING] = 'go-down'
self._status_ids[download.DownloadTask.DONE] = Gtk.STOCK_APPLY
self._status_ids[download.DownloadTask.DONE] = 'object-select-symbolic'
self._status_ids[download.DownloadTask.FAILED] = 'dialog-error'
self._status_ids[download.DownloadTask.CANCELLING] = 'media-playback-stop'
self._status_ids[download.DownloadTask.CANCELLED] = 'media-playback-stop'

View File

@ -403,8 +403,6 @@ def get_foreground_color(state=Gtk.StateFlags.NORMAL, widget=Gtk.TreeView()):
"""
p = widget
color = Gdk.RGBA(0, 0, 0, 0)
style_context = widget.get_style_context()
foreground = style_context.get_color(state)
while p is not None and color.alpha == 0:
style_context = p.get_style_context()
color = style_context.get_color(state)

View File

@ -130,11 +130,11 @@ class BuilderWidget(GtkBuilderWidget):
return response == Gtk.ResponseType.YES
def show_text_edit_dialog(self, title, prompt, text=None, empty=False,
is_url=False, affirmative_text=Gtk.STOCK_OK):
is_url=False, affirmative_text=_('_OK')):
dialog = Gtk.Dialog(title, self.get_dialog_parent(),
Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT)
dialog.add_button(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL)
dialog.add_button(_('_Cancel'), Gtk.ResponseType.CANCEL)
dialog.add_button(affirmative_text, Gtk.ResponseType.OK)
dialog.set_default_size(300, -1)
@ -270,8 +270,8 @@ class BuilderWidget(GtkBuilderWidget):
initial_directory = os.path.expanduser('~')
dlg = Gtk.FileChooserDialog(title=title, parent=self.main_window, action=Gtk.FileChooserAction.SELECT_FOLDER)
dlg.add_button(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL)
dlg.add_button(Gtk.STOCK_SAVE, Gtk.ResponseType.OK)
dlg.add_button(_('_Cancel'), Gtk.ResponseType.CANCEL)
dlg.add_button(_('_Save'), Gtk.ResponseType.OK)
dlg.set_do_overwrite_confirmation(True)
dlg.set_current_folder(initial_directory)

View File

@ -100,7 +100,8 @@ class gPodder(BuilderWidget, dbus.service.Object):
self.extensions_actions = []
self._search_podcasts = None
self._search_episodes = None
BuilderWidget.__init__(self, None, _builder_expose={'app': app}, **kwargs)
BuilderWidget.__init__(self, None,
_gtk_properties={('gPodder', 'application'): app}, **kwargs)
self.last_episode_date_refresh = None
self.refresh_episode_dates()
@ -659,7 +660,7 @@ class gPodder(BuilderWidget, dbus.service.Object):
episodes=changes,
columns=columns,
size_attribute=None,
stock_ok_button=Gtk.STOCK_APPLY,
ok_button=_('A_pply'),
callback=execute_podcast_actions,
_config=self.config)
@ -1961,7 +1962,7 @@ class gPodder(BuilderWidget, dbus.service.Object):
def on_open_download_folder(self, item):
assert self.active_channel is not None
util.gui_open(self.active_channel.save_dir)
util.gui_open(self.active_channel.save_dir, gui=self)
def treeview_channels_show_context_menu(self, treeview, event=None):
model, paths = self.treeview_handle_context_menu_click(treeview, event)
@ -2312,6 +2313,11 @@ class gPodder(BuilderWidget, dbus.service.Object):
for episode in episodes:
episode._download_error = None
if episode.download_task is not None and episode.download_task.status == episode.download_task.FAILED:
# Cancel failed task and remove from progress list
episode.download_task.cancel()
self.cleanup_downloads()
player = self.episode_player(episode)
try:
@ -2369,7 +2375,7 @@ class gPodder(BuilderWidget, dbus.service.Object):
if 'default' in groups:
for filename in groups['default']:
logger.debug('Opening with system default: %s', filename)
util.gui_open(filename)
util.gui_open(filename, gui=self)
del groups['default']
# For each type now, go and create play commands
@ -3030,8 +3036,8 @@ class gPodder(BuilderWidget, dbus.service.Object):
if downloading:
dialog = Gtk.MessageDialog(self.gPodder, Gtk.DialogFlags.MODAL, Gtk.MessageType.QUESTION, Gtk.ButtonsType.NONE)
dialog.add_button(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL)
quit_button = dialog.add_button(Gtk.STOCK_QUIT, Gtk.ResponseType.CLOSE)
dialog.add_button(_('_Cancel'), Gtk.ResponseType.CANCEL)
quit_button = dialog.add_button(_('_Quit'), Gtk.ResponseType.CLOSE)
title = _('Quit gPodder')
message = _('You are downloading episodes. You can resume downloads the next time you start gPodder. Do you want to quit now?')
@ -3182,7 +3188,7 @@ class gPodder(BuilderWidget, dbus.service.Object):
ui_folder=os.path.join(gpodder.ui_folders[0], '..', 'adaptive'),
instructions=instructions,
episodes=episodes, selected=selected, columns=columns,
stock_ok_button=_('Delete'), callback=self.delete_episode_list,
ok_button=_('_Delete'), callback=self.delete_episode_list,
selection_buttons=selection_buttons, _config=self.config)
def on_selected_episodes_status_changed(self):
@ -3384,10 +3390,10 @@ class gPodder(BuilderWidget, dbus.service.Object):
episodes=episodes,
columns=columns,
selected=selected,
stock_ok_button='gpodder-download',
ok_button='gpodder-download',
callback=download_episodes_callback,
remove_callback=lambda e: e.mark_old(),
remove_action=_('Mark as old'),
remove_action=_('_Mark as old'),
remove_finished=self.episode_new_status_changed,
_config=self.config,
show_notification=False)
@ -3535,7 +3541,7 @@ class gPodder(BuilderWidget, dbus.service.Object):
episodes=self.channels,
columns=columns,
size_attribute=None,
stock_ok_button=_('Delete'),
ok_button=_('_Delete'),
callback=self.remove_podcast_list,
_config=self.config)
@ -3636,8 +3642,8 @@ class gPodder(BuilderWidget, dbus.service.Object):
dlg = Gtk.FileChooserDialog(title=_('Import from OPML'),
parent=self.main_window,
action=Gtk.FileChooserAction.OPEN)
dlg.add_button(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL)
dlg.add_button(Gtk.STOCK_OPEN, Gtk.ResponseType.OK)
dlg.add_button(_('_Cancel'), Gtk.ResponseType.CANCEL)
dlg.add_button(_('_Open'), Gtk.ResponseType.OK)
dlg.set_filter(self.get_opml_filter())
response = dlg.run()
filename = None
@ -3665,8 +3671,8 @@ class gPodder(BuilderWidget, dbus.service.Object):
dlg = Gtk.FileChooserDialog(title=_('Export to OPML'),
parent=self.gPodder,
action=Gtk.FileChooserAction.SAVE)
dlg.add_button(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL)
dlg.add_button(Gtk.STOCK_SAVE, Gtk.ResponseType.OK)
dlg.add_button(_('_Cancel'), Gtk.ResponseType.CANCEL)
dlg.add_button(_('_Save'), Gtk.ResponseType.OK)
dlg.set_filter(self.get_opml_filter())
response = dlg.run()
if response == Gtk.ResponseType.OK:

View File

@ -198,7 +198,7 @@ class EpisodeListModel(Gtk.ListStore):
self.ICON_VIDEO_FILE = 'video-x-generic'
self.ICON_IMAGE_FILE = 'image-x-generic'
self.ICON_GENERIC_FILE = 'text-x-generic'
self.ICON_DOWNLOADING = Gtk.STOCK_GO_DOWN
self.ICON_DOWNLOADING = 'go-down'
self.ICON_DELETED = 'edit-delete'
self.ICON_ERROR = 'dialog-error'

View File

@ -31,7 +31,7 @@ from gpodder.gtkui.draw import (draw_text_box_centered, get_background_color,
import gi # isort:skip
gi.require_version('Gdk', '3.0') # isort:skip
gi.require_version('Gtk', '3.0') # isort:skip
from gi.repository import Gdk, Gtk, Pango # isort:skip
from gi.repository import Gdk, Gio, GLib, Gtk, Pango # isort:skip
_ = gpodder.gettext
@ -438,8 +438,8 @@ class gPodderShownotesHTML(gPodderShownotes):
decision.use()
return False
def on_open_in_browser(self, action):
util.open_website(action.url)
def on_open_in_browser(self, action, var):
util.open_website(var.get_string())
def on_authenticate(self, view, request):
if request.is_retry():
@ -469,10 +469,10 @@ class gPodderShownotesHTML(gPodderShownotes):
return False
def create_open_item(self, name, label, url):
action = Gtk.Action.new(name, label, None, Gtk.STOCK_OPEN)
action.url = url
action = Gio.SimpleAction.new(name, GLib.VariantType.new('s'))
action.connect('activate', self.on_open_in_browser)
return WebKit2.ContextMenuItem.new(action)
var = GLib.Variant.new_string(url)
return WebKit2.ContextMenuItem.new_from_gaction(action, label, var)
def get_stylesheet(self):
if self.stylesheet is None:

View File

@ -30,7 +30,6 @@ import os.path
import threading
import time
from enum import Enum
from os import sync
from re import S
from urllib.parse import urlparse
@ -38,8 +37,8 @@ import gpodder
from gpodder import download, services, util
import gi # isort:skip
gi.require_version('Gtk', '3.0') # isort:skip
from gi.repository import GLib, Gio, Gtk # isort:skip
gi.require_version('Gio', '2.0') # isort:skip
from gi.repository import GLib, Gio # isort:skip
logger = logging.getLogger(__name__)
@ -727,7 +726,7 @@ class SyncTask(download.DownloadTask):
# An object representing the synchronization task of an episode
# Possible states this sync task can be in
STATUS_MESSAGE = (_('Queued'), _('Queued'), _('Downloading'),
STATUS_MESSAGE = (_('Queued'), _('Queued'), _('Syncing'),
_('Finished'), _('Failed'), _('Cancelling'), _('Cancelled'), _('Pausing'), _('Paused'))
(NEW, QUEUED, DOWNLOADING, DONE, FAILED, CANCELLING, CANCELLED, PAUSING, PAUSED) = list(range(9))

View File

@ -161,7 +161,8 @@ def is_absolute_url(url):
"""
try:
parsed = urllib.parse.urlparse(url)
return not not parsed.scheme
# fix #1190: when parsing a windows path, scheme=drive_letter, path=\rest_of_path
return parsed.scheme and not parsed.path.startswith("\\")
except ValueError:
return False
@ -775,6 +776,10 @@ class ExtractHyperlinkedText(object):
return self.extracter.get_result()
def visit(self, element):
# skip functions generated by html5lib for comments in the HTML
if callable(element.tag):
return
NS = '{http://www.w3.org/1999/xhtml}'
tag_name = (element.tag[len(NS):] if element.tag.startswith(NS) else element.tag).lower()
self.extracter.handle_starttag(tag_name, list(element.items()))
@ -1465,7 +1470,7 @@ def http_request(url, method='HEAD'):
return conn.getresponse()
def gui_open(filename):
def gui_open(filename, gui=None):
"""
Open a file or folder with the default application set
by the Desktop environment. This uses "xdg-open" on all
@ -1476,13 +1481,30 @@ def gui_open(filename):
try:
if gpodder.ui.win32:
os.startfile(filename)
opener = None
elif gpodder.ui.osx:
Popen(['open', filename], close_fds=True)
opener = 'open'
else:
Popen(['xdg-open', filename], close_fds=True)
opener = 'xdg-open'
if opener:
opener_fullpath = shutil.which(opener)
if opener_fullpath is None:
raise Exception((_("System default program '%(opener)s' not found"))
% {'opener': opener}
)
Popen([opener_fullpath, filename], close_fds=True)
return True
except:
logger.error('Cannot open file/folder: "%s"', filename, exc_info=True)
if gui is not None:
if opener is None:
message = _("Cannot open file/folder '%(filename)s' using default program") % {'filename': filename}
else:
message = _("Cannot open '%(filename)s' using '%(opener)s'") \
% {'filename': filename, 'opener': opener}
gui.show_message_details(_('Cannot open file/folder'),
str(sys.exc_info()[1]), message)
return False

View File

@ -147,8 +147,21 @@ hls_formats = [
]
hls_formats_dict = dict(hls_formats)
V3_API_ENDPOINT = 'https://www.googleapis.com/youtube/v3'
CHANNEL_VIDEOS_XML = 'https://www.youtube.com/feeds/videos.xml'
WATCH_ENDPOINT = 'https://www.youtube.com/watch?bpctr=9999999999&has_verified=1&v='
# The page may contain "};" sequences inside the initial player response.
# Use a greedy match with script end tag, and fallback to a non-greedy match without.
INITIAL_PLAYER_RESPONSE_RE1 = r'ytInitialPlayerResponse\s*=\s*({.+})\s*;\s*</script'
INITIAL_PLAYER_RESPONSE_RE2 = r'ytInitialPlayerResponse\s*=\s*({.+?})\s*;'
def get_ipr(page):
for regex in (INITIAL_PLAYER_RESPONSE_RE1, INITIAL_PLAYER_RESPONSE_RE2):
ipr = re.search(regex, page)
if ipr is not None:
return ipr
return None
class YouTubeError(Exception):
@ -198,12 +211,12 @@ def youtube_get_old_endpoint(vid):
def youtube_get_new_endpoint(vid):
url = 'https://www.youtube.com/watch?bpctr=9999999999&has_verified=1&v=' + vid
url = WATCH_ENDPOINT + vid
r = util.urlopen(url)
if not r.ok:
raise YouTubeError('Youtube "%s": %d %s' % (url, r.status_code, r.reason))
ipr = re.search(r'ytInitialPlayerResponse\s*=\s*({.+?})\s*;', r.text)
ipr = get_ipr(r.text)
if ipr is None:
try:
url = get_gdpr_consent_url(r.text)
@ -213,7 +226,7 @@ def youtube_get_new_endpoint(vid):
if not r.ok:
raise YouTubeError('Youtube "%s": %d %s' % (url, r.status_code, r.reason))
ipr = re.search(r'ytInitialPlayerResponse\s*=\s*({.+?})\s*;', r.text)
ipr = get_ipr(r.text)
if ipr is None:
raise YouTubeError('Youtube "%s": No ytInitialPlayerResponse found' % url)
@ -226,19 +239,19 @@ def get_total_time(episode):
if vid is None:
return 0
url = 'https://www.youtube.com/watch?v=' + vid
url = WATCH_ENDPOINT + vid
r = util.urlopen(url)
if not r.ok:
return 0
ipr = re.search(r'ytInitialPlayerResponse\s*=\s*({.+?})\s*;', r.text)
ipr = get_ipr(r.text)
if ipr is None:
url = get_gdpr_consent_url(r.text)
r = util.urlopen(url)
if not r.ok:
return 0
ipr = re.search(r'ytInitialPlayerResponse\s*=\s*({.+?})\s*;', r.text)
ipr = get_ipr(r.text)
if ipr is None:
return 0
@ -354,10 +367,6 @@ def get_real_download_url(url, allow_partial, preferred_fmt_ids=None):
def get_youtube_id(url):
r = re.compile(r'http[s]?://(?:[a-z]+\.)?youtube\.com/v/(.*)\.swf', re.IGNORECASE).match(url)
if r is not None:
return r.group(1)
r = re.compile(r'http[s]?://(?:[a-z]+\.)?youtube\.com/watch\?v=([^&]*)', re.IGNORECASE).match(url)
if r is not None:
return r.group(1)
@ -366,6 +375,10 @@ def get_youtube_id(url):
if r is not None:
return r.group(1)
r = re.compile(r'http[s]?://(?:[a-z]+\.)?youtube\.com/v/(.*)\.swf', re.IGNORECASE).match(url)
if r is not None:
return r.group(1)
return for_each_feed_pattern(lambda url, channel: channel, url, None)
@ -389,6 +402,7 @@ def for_each_feed_pattern(func, url, fallback_result):
r'http[s]?://(?:[a-z]+\.)?youtube\.com/profile?user=([a-z0-9]+)',
r'http[s]?://(?:[a-z]+\.)?youtube\.com/rss/user/([a-z0-9]+)/videos\.rss',
r'http[s]?://(?:[a-z]+\.)?youtube\.com/channel/([-_a-z0-9]+)',
r'http[s]?://(?:[a-z]+\.)?youtube\.com/feeds/videos.xml\?user=([a-z0-9]+)',
r'http[s]?://(?:[a-z]+\.)?youtube\.com/feeds/videos.xml\?channel_id=([-_a-z0-9]+)',
r'http[s]?://gdata.youtube.com/feeds/users/([^/]+)/uploads',
r'http[s]?://gdata.youtube.com/feeds/base/users/([^/]+)/uploads',

View File

@ -97,6 +97,7 @@ urllib3==1.26.5
chardet==4.0.0
idna==3.2
PySocks==1.7.1
comtypes==1.1.10
"
function install_deps {