Cover art: Unified download manager (bug 1288)

This commit is contained in:
Thomas Perl 2012-03-05 10:44:19 +01:00
parent 2ef72dddff
commit c9d5e7208d
23 changed files with 451 additions and 566 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 6.2 KiB

View File

@ -1,646 +1,454 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<!-- interface-requires gtk+ 2.12 -->
<!-- interface-naming-policy toplevel-contextual -->
<object class="GtkDialog" id="gPodderChannel">
<property name="can_focus">False</property>
<property name="border_width">10</property>
<property name="visible">False</property>
<property name="has_separator">False</property>
<property name="title" translatable="yes">gPodder Podcast Editor</property>
<property name="window_position">GTK_WIN_POS_CENTER_ON_PARENT</property>
<property name="modal">True</property>
<property name="window_position">center-on-parent</property>
<property name="default_width">500</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>
<signal handler="on_gPodderChannel_destroy" name="destroy"/>
<property name="default_height">400</property>
<property name="type_hint">dialog</property>
<signal name="destroy" handler="on_gPodderChannel_destroy" swapped="no"/>
<child internal-child="vbox">
<object class="GtkVBox" id="vboxChannelEditorMain">
<property name="visible">True</property>
<property name="homogeneous">False</property>
<property name="can_focus">False</property>
<property name="spacing">5</property>
<child internal-child="action_area">
<object class="GtkHButtonBox" id="hboxButtons">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="layout_style">end</property>
<child>
<object class="GtkButton" id="btnOK">
<property name="label">gtk-close</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="has_focus">True</property>
<property name="can_default">True</property>
<property name="has_default">True</property>
<property name="receives_default">False</property>
<property name="use_action_appearance">False</property>
<property name="use_stock">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">0</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="pack_type">end</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkNotebook" id="notebookChannelEditor">
<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>
<child>
<object class="GtkTable" id="table_settings">
<property name="border_width">10</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="border_width">10</property>
<property name="n_rows">5</property>
<property name="n_columns">2</property>
<property name="homogeneous">False</property>
<property name="row_spacing">10</property>
<property name="n_columns">3</property>
<property name="column_spacing">5</property>
<child>
<object class="GtkLabel" id="label11">
<property name="visible">True</property>
<property name="label" translatable="yes">Feed URL:</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">1</property>
<property name="width_chars">-1</property>
<property name="single_line_mode">False</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="right_attach">1</property>
<property name="top_attach">1</property>
<property name="bottom_attach">2</property>
<property name="x_options">fill</property>
<property name="y_options"/>
</packing>
</child>
<property name="row_spacing">10</property>
<child>
<object class="GtkLabel" id="labelURL">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="xalign">0</property>
<property name="selectable">True</property>
<property name="xalign">0.0</property>
<property name="ellipsize">PANGO_ELLIPSIZE_END</property>
<property name="ellipsize">end</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="right_attach">3</property>
<property name="top_attach">1</property>
<property name="bottom_attach">2</property>
<property name="y_options"/>
</packing>
</child>
<child>
<object class="GtkLabel" id="labelTitle">
<property name="visible">True</property>
<property name="label" translatable="yes">Title:</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="xalign">1</property>
<property name="width_chars">-1</property>
<property name="single_line_mode">False</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="right_attach">1</property>
<property name="top_attach">0</property>
<property name="bottom_attach">1</property>
<property name="x_options">fill</property>
<property name="y_options"/>
<property name="y_options"></property>
</packing>
</child>
<child>
<object class="GtkEntry" id="entryTitle">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="max_length">0</property>
<property name="has_frame">True</property>
<property name="invisible_char">*</property>
<property name="activates_default">False</property>
<property name="primary_icon_activatable">False</property>
<property name="secondary_icon_activatable">False</property>
<property name="primary_icon_sensitive">True</property>
<property name="secondary_icon_sensitive">True</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">0</property>
<property name="bottom_attach">1</property>
<property name="y_options"/>
<property name="right_attach">3</property>
<property name="y_options"></property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label_section">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Section:</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="right_attach">1</property>
<property name="top_attach">3</property>
<property name="bottom_attach">4</property>
<property name="x_options">fill</property>
<property name="y_options"/>
</packing>
</child>
<child>
<object class="GtkComboBoxEntry" id="combo_section">
<property name="visible">True</property>
<property name="text-column">0</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">3</property>
<property name="bottom_attach">4</property>
<property name="x_options">fill</property>
<property name="y_options"/>
<property name="x_options">GTK_FILL</property>
<property name="y_options"></property>
</packing>
</child>
<child>
<object class="GtkComboBoxEntry" id="combo_section">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="text_column">0</property>
<child internal-child="entry">
<object class="GtkEntry" id="comboboxentry-entry2">
<property name="can_focus">False</property>
<property name="primary_icon_activatable">False</property>
<property name="secondary_icon_activatable">False</property>
<property name="primary_icon_sensitive">True</property>
<property name="secondary_icon_sensitive">True</property>
</object>
</child>
</object>
<packing>
<property name="left_attach">2</property>
<property name="right_attach">3</property>
<property name="top_attach">3</property>
<property name="bottom_attach">4</property>
<property name="y_options"></property>
</packing>
</child>
<child>
<object class="GtkScrolledWindow" id="scrolledwindow">
<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="hscrollbar_policy">automatic</property>
<property name="vscrollbar_policy">automatic</property>
<property name="shadow_type">in</property>
<child>
<object class="GtkTextView" id="channel_description">
<property name="visible">True</property>
<property name="editable">False</property>
<property name="can_focus">True</property>
<property name="overwrite">False</property>
<property name="accepts_tab">True</property>
<property name="wrap_mode">GTK_WRAP_WORD</property>
<property name="cursor_visible">True</property>
<property name="pixels_above_lines">0</property>
<property name="pixels_below_lines">0</property>
<property name="pixels_inside_wrap">0</property>
<property name="left_margin">0</property>
<property name="right_margin">0</property>
<property name="editable">False</property>
<property name="wrap_mode">word</property>
</object>
</child>
</object>
<packing>
<property name="left_attach">0</property>
<property name="right_attach">2</property>
<property name="right_attach">3</property>
<property name="top_attach">4</property>
<property name="bottom_attach">5</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="cbSkipFeedUpdate">
<property name="label" translatable="yes">Disable feed updates (pause subscription)</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="label" translatable="yes">Disable feed updates (pause subscription)</property>
<property name="receives_default">False</property>
<property name="use_action_appearance">False</property>
<property name="use_underline">True</property>
<property name="focus_on_click">True</property>
<property name="active">False</property>
<property name="inconsistent">False</property>
<property name="draw_indicator">True</property>
</object>
<packing>
<property name="right_attach">2</property>
<property name="left_attach">1</property>
<property name="right_attach">3</property>
<property name="top_attach">2</property>
<property name="bottom_attach">3</property>
<property name="y_options">fill</property>
<property name="y_options">GTK_FILL</property>
</packing>
</child>
<child>
<object class="GtkEventBox" id="imgCoverEventBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkImage" id="imgCover">
<property name="visible">True</property>
<property name="can_focus">False</property>
</object>
</child>
</object>
<packing>
<property name="bottom_attach">4</property>
<property name="x_options">GTK_FILL</property>
<property name="y_options">GTK_FILL</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="label19">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">General</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="tab_fill">False</property>
</packing>
</child>
<child>
<object class="GtkVBox" id="vboxiPodProperties">
<property name="border_width">10</property>
<property name="visible">True</property>
<property name="homogeneous">False</property>
<property name="can_focus">False</property>
<property name="border_width">10</property>
<property name="spacing">5</property>
<child>
<object class="GtkLabel" id="label96">
<property name="visible">True</property>
<property name="label" translatable="yes">&lt;b&gt;HTTP/FTP Authentication&lt;/b&gt;</property>
<property name="use_underline">False</property>
<property name="use_markup">True</property>
<property name="wrap">False</property>
<property name="selectable">False</property>
<property name="can_focus">False</property>
<property name="xalign">0</property>
<property name="width_chars">-1</property>
<property name="single_line_mode">False</property>
<property name="label" translatable="yes">&lt;b&gt;HTTP/FTP Authentication&lt;/b&gt;</property>
<property name="use_markup">True</property>
</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="GtkTable" id="table10">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="n_rows">2</property>
<property name="n_columns">2</property>
<property name="homogeneous">False</property>
<property name="row_spacing">6</property>
<property name="column_spacing">6</property>
<property name="row_spacing">6</property>
<child>
<object class="GtkLabel" id="label93">
<property name="visible">True</property>
<property name="label" translatable="yes">Username:</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="can_focus">False</property>
<property name="xalign">0</property>
<property name="width_chars">-1</property>
<property name="single_line_mode">False</property>
<property name="label" translatable="yes">Username:</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="right_attach">1</property>
<property name="top_attach">0</property>
<property name="bottom_attach">1</property>
<property name="x_options">fill</property>
<property name="y_options"/>
<property name="x_options">GTK_FILL</property>
<property name="y_options"></property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label94">
<property name="visible">True</property>
<property name="label" translatable="yes">Password:</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="can_focus">False</property>
<property name="xalign">0</property>
<property name="width_chars">-1</property>
<property name="single_line_mode">False</property>
<property name="label" translatable="yes">Password:</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="right_attach">1</property>
<property name="top_attach">1</property>
<property name="bottom_attach">2</property>
<property name="x_options">fill</property>
<property name="y_options"/>
<property name="x_options">GTK_FILL</property>
<property name="y_options"></property>
</packing>
</child>
<child>
<object class="GtkEntry" id="FeedUsername">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="max_length">0</property>
<property name="has_frame">True</property>
<property name="invisible_char">●</property>
<property name="activates_default">False</property>
<property name="primary_icon_activatable">False</property>
<property name="secondary_icon_activatable">False</property>
<property name="primary_icon_sensitive">True</property>
<property name="secondary_icon_sensitive">True</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">0</property>
<property name="bottom_attach">1</property>
<property name="y_options"/>
<property name="y_options"></property>
</packing>
</child>
<child>
<object class="GtkEntry" id="FeedPassword">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="max_length">0</property>
<property name="has_frame">True</property>
<property name="invisible_char">●</property>
<property name="activates_default">False</property>
<property name="primary_icon_activatable">False</property>
<property name="secondary_icon_activatable">False</property>
<property name="primary_icon_sensitive">True</property>
<property name="secondary_icon_sensitive">True</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">1</property>
<property name="bottom_attach">2</property>
<property name="y_options"/>
<property name="y_options"></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>
<child>
<object class="GtkHSeparator" id="hseparator13">
<property name="visible">True</property>
<property name="can_focus">False</property>
</object>
<packing>
<property name="padding">0</property>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label97">
<property name="visible">True</property>
<property name="label" translatable="yes">&lt;b&gt;Locations&lt;/b&gt;</property>
<property name="use_underline">False</property>
<property name="use_markup">True</property>
<property name="wrap">False</property>
<property name="selectable">False</property>
<property name="can_focus">False</property>
<property name="xalign">0</property>
<property name="width_chars">-1</property>
<property name="single_line_mode">False</property>
<property name="label" translatable="yes">&lt;b&gt;Locations&lt;/b&gt;</property>
<property name="use_markup">True</property>
</object>
<packing>
<property name="padding">0</property>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">3</property>
</packing>
</child>
<child>
<object class="GtkTable" id="table3">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="n_rows">2</property>
<property name="n_columns">2</property>
<property name="homogeneous">False</property>
<property name="row_spacing">6</property>
<property name="n_columns">3</property>
<property name="column_spacing">6</property>
<property name="row_spacing">6</property>
<child>
<object class="GtkLabel" id="label29">
<property name="visible">True</property>
<property name="label" translatable="yes">Download to:</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="can_focus">False</property>
<property name="xalign">0</property>
<property name="width_chars">-1</property>
<property name="single_line_mode">False</property>
<property name="label" translatable="yes">Download to:</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="right_attach">1</property>
<property name="top_attach">0</property>
<property name="bottom_attach">1</property>
<property name="x_options">fill</property>
<property name="y_options"/>
<property name="x_options">GTK_FILL</property>
<property name="y_options"></property>
</packing>
</child>
<child>
<object class="GtkLabel" id="LabelDownloadTo">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="label">download to label</property>
<property name="use_underline">False</property>
<property name="use_markup">False</property>
<property name="wrap">False</property>
<property name="selectable">True</property>
<property name="xalign">0</property>
<property name="ellipsize">PANGO_ELLIPSIZE_START</property>
<property name="width_chars">-1</property>
<property name="single_line_mode">False</property>
<property name="label">download to label</property>
<property name="selectable">True</property>
<property name="ellipsize">start</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">0</property>
<property name="bottom_attach">1</property>
<property name="y_options"/>
<property name="right_attach">3</property>
<property name="y_options"></property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label54">
<property name="visible">True</property>
<property name="label" translatable="yes">Website:</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="can_focus">False</property>
<property name="xalign">0</property>
<property name="width_chars">-1</property>
<property name="single_line_mode">False</property>
<property name="label" translatable="yes">Website:</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="right_attach">1</property>
<property name="top_attach">1</property>
<property name="bottom_attach">2</property>
<property name="x_options">fill</property>
<property name="y_options"/>
<property name="x_options">GTK_FILL</property>
<property name="y_options"></property>
</packing>
</child>
<child>
<object class="GtkLabel" id="LabelWebsite">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="label" translatable="yes">website label</property>
<property name="use_underline">False</property>
<property name="use_markup">False</property>
<property name="wrap">False</property>
<property name="selectable">True</property>
<property name="xalign">0</property>
<property name="ellipsize">PANGO_ELLIPSIZE_END</property>
<property name="width_chars">-1</property>
<property name="single_line_mode">False</property>
<property name="label" translatable="yes">website label</property>
<property name="selectable">True</property>
<property name="ellipsize">end</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">1</property>
<property name="bottom_attach">2</property>
<property name="y_options"/>
<property name="y_options"></property>
</packing>
</child>
<child>
<object class="GtkButton" id="btn_website">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="use_action_appearance">False</property>
<signal name="clicked" handler="on_btn_website_clicked" swapped="no"/>
<child>
<object class="GtkImage" id="image3167">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="icon_name">web-browser</property>
</object>
</child>
</object>
<packing>
<property name="left_attach">2</property>
<property name="right_attach">3</property>
<property name="top_attach">1</property>
<property name="bottom_attach">2</property>
<property name="x_options">GTK_FILL</property>
</packing>
</child>
</object>
<packing>
<property name="padding">0</property>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">4</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="label37">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Advanced</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="GtkVBox" id="vboxCoverEditor">
<property name="border_width">10</property>
<property name="visible">True</property>
<property name="homogeneous">False</property>
<property name="spacing">6</property>
<child>
<object class="GtkImage" id="imgCover">
<property name="visible">True</property>
</object>
<packing>
<property name="padding">0</property>
<property name="expand">True</property>
<property name="fill">True</property>
</packing>
</child>
<child>
<object class="GtkHButtonBox" id="hbuttonbox1">
<property name="visible">True</property>
<property name="layout_style">GTK_BUTTONBOX_END</property>
<property name="spacing">6</property>
<child>
<object class="GtkButton" id="btnDownloadCover">
<property name="visible">True</property>
<property name="can_default">True</property>
<property name="can_focus">True</property>
<property name="use_stock">True</property>
<property name="label">gtk-open</property>
<property name="focus_on_click">True</property>
<signal handler="on_btnDownloadCover_clicked" name="clicked"/>
</object>
</child>
<child>
<object class="GtkButton" id="btnClearCover">
<property name="visible">True</property>
<property name="can_default">True</property>
<property name="can_focus">True</property>
<property name="label">gtk-refresh</property>
<property name="use_stock">True</property>
<property name="focus_on_click">True</property>
<signal handler="on_btnClearCover_clicked" name="clicked"/>
</object>
</child>
</object>
<packing>
<property name="padding">0</property>
<property name="expand">False</property>
<property name="fill">True</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="label31">
<property name="visible">True</property>
<property name="label" translatable="yes">Cover</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>
</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="GtkHBox" id="hboxButtons">
<property name="visible">True</property>
<property name="homogeneous">False</property>
<property name="spacing">5</property>
<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="has_focus">True</property>
<property name="label">gtk-close</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>
<property name="pack_type">GTK_PACK_END</property>
<property name="position">1</property>
<property name="tab_fill">False</property>
</packing>
</child>
<child>
<placeholder/>
</child>
<child>
<object class="GtkButton" id="btn_website">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="focus_on_click">True</property>
<signal handler="on_btn_website_clicked" name="clicked"/>
<child>
<object class="GtkAlignment" id="alignment26">
<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>
<child>
<object class="GtkHBox" id="hbox40">
<property name="visible">True</property>
<property name="homogeneous">False</property>
<property name="spacing">2</property>
<child>
<object class="GtkImage" id="image3167">
<property name="visible">True</property>
<property name="icon_size">4</property>
<property name="icon_name">web-browser</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="label123">
<property name="visible">True</property>
<property name="label" translatable="yes">Go to website</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>
</object>
</child>
</object>
</child>
</object>
<packing>
<property name="padding">0</property>
<property name="expand">False</property>
<property name="fill">False</property>
</packing>
<child type="tab">
<placeholder/>
</child>
</object>
<packing>
<property name="padding">0</property>
<property name="expand">False</property>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
</child>
<action-widgets>
<action-widget response="0">btnOK</action-widget>
</action-widgets>
</object>
</interface>

117
src/gpodder/coverart.py Normal file
View File

@ -0,0 +1,117 @@
# -*- coding: utf-8 -*-
#
# gPodder - A media aggregator and podcast client
# Copyright (c) 2005-2012 Thomas Perl and the gPodder Team
#
# gPodder is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# gPodder is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
#
# gpodder.coverart - Unified cover art downloading module (2012-03-04)
#
import gpodder
_ = gpodder.gettext
import logging
logger = logging.getLogger(__name__)
from gpodder import util
from gpodder import youtube
import os
class CoverDownloader(object):
# File name extension dict, lists supported cover art extensions
# Values: functions that check if some data is of that file type
SUPPORTED_EXTENSIONS = {
'.png': lambda d: d.startswith('\x89PNG\r\n\x1a\n\x00'),
'.jpg': lambda d: d.startswith('\xff\xd8'),
}
EXTENSIONS = SUPPORTED_EXTENSIONS.keys()
ALL_EPISODES_ID = ':gpodder:all-episodes:'
def __init__(self):
pass
def get_cover_all_episodes(self):
return self._default_filename('podcast-all.png')
def get_cover(self, filename, cover_url, feed_url, title,
username=None, password=None, download=False):
# Detection of "all episodes" podcast
if filename == self.ALL_EPISODES_ID:
return self.get_cover_all_episodes()
# Return already existing files
for extension in self.EXTENSIONS:
if os.path.exists(filename + extension):
return filename + extension
# If allowed to download files, do so here
if download:
# YouTube-specific cover art image resolver
youtube_cover_url = youtube.get_real_cover(feed_url)
if youtube_cover_url is not None:
cover_url = youtube_cover_url
if not cover_url:
return self._fallback_filename(title)
# We have to add username/password, because password-protected
# feeds might keep their cover art also protected (bug 1521)
if username is not None and password is not None:
cover_url = util.url_add_authentication(cover_url,
username, password)
try:
logger.info('Downloading cover art: %s', cover_url)
data = util.urlopen(cover_url).read()
except Exception, e:
logger.warn('Cover art download failed: %s', e)
return self._fallback_filename(title)
try:
extension = None
for filetype, check in self.SUPPORTED_EXTENSIONS.items():
if check(data):
extension = filetype
break
if extension is None:
msg = 'Unknown file type: %s (%r)' % (cover_url, data[:6])
raise ValueError(msg)
# Successfully downloaded the cover art - save it!
fp = open(filename + extension, 'wb')
fp.write(data)
fp.close()
return filename + extension
except Exception, e:
logger.warn('Cannot save cover art', exc_info=True)
# Fallback to cover art based on the podcast title
return self._fallback_filename(title)
def _default_filename(self, basename):
return os.path.join(gpodder.images_folder, basename)
def _fallback_filename(self, title):
return self._default_filename('podcast-%d.png' % (hash(title)%5))

View File

@ -81,7 +81,7 @@ class DBusPodcastsProxy(dbus.service.Object):
title = safe_str(podcast.title)
url = safe_str(podcast.url)
description = safe_first_line(podcast.description)
cover_file = safe_str(podcast.cover_file)
cover_file = ''
return (title, url, description, cover_file)

View File

@ -29,6 +29,8 @@ from gpodder.gtkui.interface.common import BuilderWidget
class gPodderChannel(BuilderWidget):
MAX_SIZE = 120
def new(self):
self.gPodderChannel.set_title( self.channel.title)
self.entryTitle.set_text( self.channel.title)
@ -62,10 +64,31 @@ class gPodderChannel(BuilderWidget):
#Add Drag and Drop Support
flags = gtk.DEST_DEFAULT_ALL
targets = [ ('text/uri-list', 0, 2), ('text/plain', 0, 4) ]
targets = [('text/uri-list', 0, 2), ('text/plain', 0, 4)]
actions = gtk.gdk.ACTION_DEFAULT | gtk.gdk.ACTION_COPY
self.vboxCoverEditor.drag_dest_set( flags, targets, actions)
self.vboxCoverEditor.connect( 'drag_data_received', self.drag_data_received)
self.imgCover.drag_dest_set(flags, targets, actions)
self.imgCover.connect('drag_data_received', self.drag_data_received)
border = 6
self.imgCover.set_size_request(*((self.MAX_SIZE+border*2,)*2))
self.imgCoverEventBox.connect('button-press-event',
self.on_cover_popup_menu)
def on_cover_popup_menu(self, widget, event):
if event.button != 3:
return
menu = gtk.Menu()
item = gtk.ImageMenuItem(gtk.STOCK_OPEN)
item.connect('activate', self.on_btnDownloadCover_clicked)
menu.append(item)
item = gtk.ImageMenuItem(gtk.STOCK_REFRESH)
item.connect('activate', self.on_btnClearCover_clicked)
menu.append(item)
menu.show_all()
menu.popup(None, None, None, event.button, event.time, None)
def on_btn_website_clicked(self, widget):
util.open_website(self.channel.link)
@ -77,17 +100,21 @@ class gPodderChannel(BuilderWidget):
if dlg.run() == gtk.RESPONSE_OK:
url = dlg.get_uri()
self.cover_downloader.replace_cover(self.channel, url)
self.clear_cover_cache(self.channel.url)
self.cover_downloader.replace_cover(self.channel, custom_url=url)
dlg.destroy()
def on_btnClearCover_clicked(self, widget):
self.cover_downloader.replace_cover(self.channel)
self.clear_cover_cache(self.channel.url)
self.cover_downloader.replace_cover(self.channel, custom_url=False)
def cover_download_finished(self, channel_url, pixbuf):
if pixbuf is not None:
self.imgCover.set_from_pixbuf(pixbuf)
self.gPodderChannel.show()
def cover_download_finished(self, channel, pixbuf):
def set_cover(channel, pixbuf):
self.imgCover.set_from_pixbuf(self.scale_pixbuf(pixbuf))
self.gPodderChannel.show()
util.idle_add(set_cover, channel, pixbuf)
def drag_data_received( self, widget, content, x, y, sel, ttype, time):
files = sel.data.strip().split('\n')
@ -98,7 +125,8 @@ class gPodderChannel(BuilderWidget):
file = files[0]
if file.startswith('file://') or file.startswith('http://'):
self.cover_downloader.replace_cover(self.channel, file)
self.clear_cover_cache(self.channel.url)
self.cover_downloader.replace_cover(self.channel, custom_url=file)
return
self.show_message( _('You can only drop local files and http:// URLs here.'), _('Drag and drop'))
@ -106,12 +134,31 @@ class gPodderChannel(BuilderWidget):
def on_gPodderChannel_destroy(self, widget, *args):
self.cover_downloader.unregister('cover-available', self.cover_download_finished)
def scale_pixbuf(self, pixbuf):
# Resize if width is too large
if pixbuf.get_width() > self.MAX_SIZE:
f = float(self.MAX_SIZE)/pixbuf.get_width()
(width, height) = (int(pixbuf.get_width()*f), int(pixbuf.get_height()*f))
pixbuf = pixbuf.scale_simple(width, height, gtk.gdk.INTERP_BILINEAR)
# Resize if height is too large
if pixbuf.get_height() > self.MAX_SIZE:
f = float(self.MAX_SIZE)/pixbuf.get_height()
(width, height) = (int(pixbuf.get_width()*f), int(pixbuf.get_height()*f))
pixbuf = pixbuf.scale_simple(width, height, gtk.gdk.INTERP_BILINEAR)
return pixbuf
def on_btnOK_clicked(self, widget, *args):
self.channel.pause_subscription = self.cbSkipFeedUpdate.get_active()
self.channel.rename(self.entryTitle.get_text())
self.channel.auth_username = self.FeedUsername.get_text().strip()
self.channel.auth_password = self.FeedPassword.get_text()
self.clear_cover_cache(self.channel.url)
self.cover_downloader.request_cover(self.channel)
new_section = self.combo_section.child.get_text().strip()
if self.channel.section != new_section:
self.channel.section = new_section
@ -121,8 +168,6 @@ class gPodderChannel(BuilderWidget):
self.channel.save()
self.cover_downloader.reload_cover_from_disk(self.channel)
self.gPodderChannel.destroy()
self.update_podcast_list_model(selected=True,

View File

@ -183,7 +183,6 @@ class gPodder(BuilderWidget, dbus.service.Object):
self.podcast_list_model = PodcastListModel(self.cover_downloader)
self.cover_downloader.register('cover-available', self.cover_download_finished)
self.cover_downloader.register('cover-removed', self.cover_file_removed)
# Source IDs for timeouts for search-as-you-type
self._podcast_list_search_timeout = None
@ -1533,7 +1532,7 @@ class gPodder(BuilderWidget, dbus.service.Object):
menu.append( gtk.SeparatorMenuItem())
item = gtk.ImageMenuItem(_('Podcast details'))
item = gtk.ImageMenuItem(_('Podcast settings'))
item.set_image(gtk.image_new_from_stock(gtk.STOCK_INFO, gtk.ICON_SIZE_MENU))
item.connect('activate', self.on_itemEditChannel_activate)
menu.append(item)
@ -1552,21 +1551,14 @@ class gPodder(BuilderWidget, dbus.service.Object):
return True
def cover_file_removed(self, channel_url):
"""
The Cover Downloader calls this when a previously-
available cover has been removed from the disk. We
have to update our model to reflect this change.
"""
self.podcast_list_model.delete_cover_by_url(channel_url)
def cover_download_finished(self, channel, pixbuf):
"""
The Cover Downloader calls this when it has finished
downloading (or registering, if already downloaded)
a new channel cover, which is ready for displaying.
"""
self.podcast_list_model.add_cover_by_channel(channel, pixbuf)
util.idle_add(self.podcast_list_model.add_cover_by_channel,
channel, pixbuf)
def save_episodes_as_file(self, episodes):
for episode in episodes:
@ -2284,7 +2276,7 @@ class gPodder(BuilderWidget, dbus.service.Object):
self.db.commit()
def _update_cover(self, channel):
if channel is not None and not os.path.exists(channel.cover_file) and channel.image:
if channel is not None:
self.cover_downloader.request_cover(channel)
def show_update_feeds_buttons(self):
@ -2877,11 +2869,12 @@ class gPodder(BuilderWidget, dbus.service.Object):
self.show_message( message, title, widget=self.treeChannels)
return
gPodderChannel(self.main_window, \
channel=self.active_channel, \
update_podcast_list_model=self.update_podcast_list_model, \
cover_downloader=self.cover_downloader, \
sections=set(c.section for c in self.channels))
gPodderChannel(self.main_window,
channel=self.active_channel,
update_podcast_list_model=self.update_podcast_list_model,
cover_downloader=self.cover_downloader,
sections=set(c.section for c in self.channels),
clear_cover_cache=self.podcast_list_model.clear_cover_cache)
def on_itemMassUnsubscribe_activate(self, item=None):
columns = (

View File

@ -30,6 +30,7 @@ _ = gpodder.gettext
from gpodder import util
from gpodder import model
from gpodder import query
from gpodder import coverart
import logging
logger = logging.getLogger(__name__)
@ -429,7 +430,10 @@ class PodcastChannelProxy(object):
self.url = ''
self.section = ''
self.id = None
self.cover_file = util.podcast_image_filename('podcast-all.png')
self.cover_file = coverart.CoverDownloader.ALL_EPISODES_ID
self.cover_url = None
self.auth_username = None
self.auth_password = None
self.pause_subscription = False
self.auto_archive_episodes = False
@ -779,6 +783,11 @@ class PodcastListModel(gtk.ListStore):
self.C_HAS_EPISODES, total > 0, \
self.C_DOWNLOADS, downloaded)
def clear_cover_cache(self, podcast_url):
if podcast_url in self._cover_cache:
logger.info('Clearing cover from cache: %s', podcast_url)
del self._cover_cache[podcast_url]
def add_cover_by_channel(self, channel, pixbuf):
# Resize and add the new cover image
pixbuf = self._resize_pixbuf(channel.url, pixbuf)
@ -791,14 +800,3 @@ class PodcastListModel(gtk.ListStore):
row[self.C_COVER] = pixbuf
break
def delete_cover_by_url(self, url):
# Remove the cover from the model
for row in self:
if row[self.C_URL] == url:
row[self.C_COVER] = None
break
# Remove the cover from the cache
if url in self._cover_cache:
del self._cover_cache[url]

View File

@ -32,11 +32,9 @@ import logging
logger = logging.getLogger(__name__)
from gpodder import util
from gpodder import youtube
from gpodder import coverart
import gtk
import os
import urlparse
import threading
@ -50,10 +48,8 @@ class CoverDownloader(ObservableService):
'cover-available' message (via the ObservableService).
"""
# Maximum width/height of the cover in pixels
MAX_SIZE = 360
def __init__(self):
self.downloader = coverart.CoverDownloader()
signal_names = ['cover-available', 'cover-removed']
ObservableService.__init__(self, signal_names)
@ -97,85 +93,39 @@ class CoverDownloader(ObservableService):
(url, pixbuf) = self.__get_cover(channel, custom_url, False, avoid_downloading)
return pixbuf
def remove_cover(self, channel):
"""
Removes the current cover for the channel
so that a new one is downloaded the next
time we request the channel cover.
"""
util.delete_file(channel.cover_file)
self.notify('cover-removed', channel.url)
def replace_cover(self, channel, custom_url=None):
"""
This is a convenience function that deletes
the current cover file and requests a new
cover from the URL specified.
"""
self.remove_cover(channel)
self.request_cover(channel, custom_url)
def reload_cover_from_disk(self, channel):
self.notify('cover-removed', channel.url)
self.request_cover(channel, None, True)
def get_default_cover(self, channel):
filename = util.cover_fallback_filename(channel.title)
return gtk.gdk.pixbuf_new_from_file(filename)
def __get_cover(self, channel, url, async=False, avoid_downloading=False):
if not async and avoid_downloading and not os.path.exists(channel.cover_file):
return (channel.url, self.get_default_cover(channel))
def get_filename():
return self.downloader.get_cover(channel.cover_file,
url or channel.cover_url, channel.url, channel.title,
channel.auth_username, channel.auth_password,
not avoid_downloading)
if not os.path.exists(channel.cover_file):
if url is None and channel.cover_url is not None:
# We have to use authenticate_url, because password-protected
# feeds might keep their cover art also protected (bug 1521)
url = channel.authenticate_url(channel.cover_url)
new_url = youtube.get_real_cover(channel.url)
if new_url is not None:
url = new_url
if url is not None:
image_data = None
try:
logger.debug('Trying to download: %s', url)
image_data = util.urlopen(url).read()
except:
logger.warn('Cannot get image from %s', url, exc_info=True)
if image_data is not None:
logger.debug('Saving image data to %s', channel.cover_file)
try:
fp = open(channel.cover_file, 'wb')
fp.write(image_data)
fp.close()
except IOError, ioe:
logger.error('Cannot save image due to I/O error', exc_info=True)
if url is not None:
filename = get_filename()
if filename.startswith(channel.cover_file):
logger.info('Replacing cover: %s', filename)
util.delete_file(filename)
filename = get_filename()
pixbuf = None
if os.path.exists(channel.cover_file):
try:
pixbuf = gtk.gdk.pixbuf_new_from_file(channel.cover_file.decode(util.encoding, 'ignore'))
except:
logger.error('Data error while loading %s', channel.cover_file)
if pixbuf is None:
pixbuf = self.get_default_cover(channel)
# Resize if width is too large
if pixbuf.get_width() > self.MAX_SIZE:
f = float(self.MAX_SIZE)/pixbuf.get_width()
(width, height) = (int(pixbuf.get_width()*f), int(pixbuf.get_height()*f))
pixbuf = pixbuf.scale_simple(width, height, gtk.gdk.INTERP_BILINEAR)
# Resize if height is too large
if pixbuf.get_height() > self.MAX_SIZE:
f = float(self.MAX_SIZE)/pixbuf.get_height()
(width, height) = (int(pixbuf.get_width()*f), int(pixbuf.get_height()*f))
pixbuf = pixbuf.scale_simple(width, height, gtk.gdk.INTERP_BILINEAR)
try:
pixbuf = gtk.gdk.pixbuf_new_from_file(filename)
except Exception, e:
logger.warn('Cannot load cover art', exc_info=True)
if filename.startswith(channel.cover_file):
logger.info('Deleting broken cover: %s', filename)
util.delete_file(filename)
filename = get_filename()
pixbuf = gtk.gdk.pixbuf_new_from_file(filename)
if async:
self.notify('cover-available', channel, pixbuf)

View File

@ -30,6 +30,7 @@ from gpodder import feedcore
from gpodder import youtube
from gpodder import vimeo
from gpodder import schema
from gpodder import coverart
import logging
logger = logging.getLogger(__name__)
@ -834,9 +835,13 @@ class PodcastChannel(PodcastModelObject):
existing_files = set(filename for filename in \
glob.glob(os.path.join(self.save_dir, '*')) \
if not filename.endswith('.partial'))
external_files = existing_files.difference(list(known_files) + \
[os.path.join(self.save_dir, x) \
for x in ('folder.jpg', 'Unknown')])
ignore_files = ['folder'+ext for ext in
coverart.CoverDownloader.EXTENSIONS] + ['Unknown']
external_files = existing_files.difference(list(known_files) +
[os.path.join(self.save_dir, ignore_file)
for ignore_file in ignore_files])
if not external_files:
return
@ -1344,7 +1349,7 @@ class PodcastChannel(PodcastModelObject):
@property
def cover_file(self):
return os.path.join(self.save_dir, 'folder.jpg')
return os.path.join(self.save_dir, 'folder')
class Model(object):

View File

@ -23,8 +23,8 @@ from PySide.QtDeclarative import QDeclarativeImageProvider
import gpodder
from gpodder import youtube
from gpodder import util
from gpodder import coverart
import logging
logger = logging.getLogger(__name__)
@ -37,6 +37,7 @@ class LocalCachedImageProvider(QDeclarativeImageProvider):
def __init__(self):
QDeclarativeImageProvider.__init__(self, self.IMAGE_TYPE)
self.downloader = coverart.CoverDownloader()
self._cache = {}
def requestImage(self, id, size, requestedSize):
@ -44,49 +45,26 @@ class LocalCachedImageProvider(QDeclarativeImageProvider):
if key in self._cache:
return self._cache[key]
filename, cover_url, url, title = (urllib.unquote(x) for x in id.split('|'))
cover_file, cover_url, podcast_url, podcast_title = (urllib.unquote(x)
for x in id.split('|'))
if filename == 'gpodder:episode-subset-view':
filename = util.podcast_image_filename('podcast-all.png', big=True)
def get_filename():
return self.downloader.get_cover(cover_file, cover_url,
podcast_url, podcast_title, None, None, True)
if '' in (filename, cover_url, url):
if filename == '' or not os.path.exists(filename):
filename = util.cover_fallback_filename(title, big=True)
data = None
if os.path.exists(filename):
data = open(filename, 'rb').read()
if data == '':
util.delete_file(filename)
filename = util.cover_fallback_filename(title, big=True)
data = open(filename, 'rb').read()
write_to_disk = True
if data is None or data == '' and cover_url:
try:
yt_url = youtube.get_real_cover(url)
if yt_url is not None:
cover_url = yt_url
data = util.urlopen(cover_url).read()
except Exception, e:
logger.error('Error downloading cover: %s', e, exc_info=True)
filename = util.cover_fallback_filename(title, big=True)
data = open(filename, 'rb').read()
write_to_disk = False
if write_to_disk:
fp = open(filename, 'wb')
fp.write(data)
fp.close()
filename = get_filename()
image = QImage()
image.loadFromData(data)
if image.isNull():
return image
else:
self._cache[key] = image.scaled(requestedSize, \
Qt.KeepAspectRatioByExpanding, \
Qt.SmoothTransformation)
return self._cache[key]
if not image.load(filename):
if filename.startswith(cover_file):
logger.info('Deleting broken cover art: %s', filename)
util.delete_file(filename)
image.load(get_filename())
if not image.isNull():
self._cache[key] = image.scaled(requestedSize,
Qt.KeepAspectRatioByExpanding,
Qt.SmoothTransformation)
return self._cache[key]

View File

@ -33,6 +33,7 @@ from gpodder import youtube
from gpodder import download
from gpodder import query
from gpodder import model
from gpodder import coverart
import threading
import os
@ -399,7 +400,7 @@ class EpisodeSubsetView(QObject):
return convert('')
def _return_cover(self):
return convert('gpodder:episode-subset-view')
return convert(coverart.CoverDownloader.ALL_EPISODES_ID)
qupdating = Property(bool, _return_false, notify=changed)
qurl = Property(unicode, _return_empty, notify=changed)

View File

@ -1575,16 +1575,6 @@ def rename_episode_file(self, episode, filename):
episode.db.commit()
def podcast_image_filename(basename, big=False):
if big:
return os.path.join(gpodder.images_folder, 'big', basename)
return os.path.join(gpodder.images_folder, basename)
def cover_fallback_filename(title, big=False):
return podcast_image_filename('podcast-%d.png' % (hash(title)%5), big)
def get_update_info(url='http://gpodder.org/downloads'):
"""
Get up to date release information from gpodder.org.