1 # -*- coding: utf-8 -*-
3 # gPodder - A media aggregator and podcast client
4 # Copyright (c) 2005-2013 Thomas Perl and the gPodder Team
6 # gPodder is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 3 of the License, or
9 # (at your option) any later version.
11 # gPodder is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program. If not, see <http://www.gnu.org/licenses/>.
28 from gpodder
import util
30 from gpodder
.gtkui
.base
import GtkBuilderWidget
33 class BuilderWidget(GtkBuilderWidget
):
34 def __init__(self
, parent
, **kwargs
):
35 self
._window
_iconified
= False
36 self
._window
_visible
= False
38 GtkBuilderWidget
.__init
__(self
, gpodder
.ui_folders
, gpodder
.textdomain
, **kwargs
)
40 # Enable support for tracking iconified state
41 if hasattr(self
, 'on_iconify') and hasattr(self
, 'on_uniconify'):
42 self
.main_window
.connect('window-state-event', \
43 self
._on
_window
_state
_event
_iconified
)
45 # Enable support for tracking visibility state
46 self
.main_window
.connect('visibility-notify-event', \
47 self
._on
_window
_state
_event
_visibility
)
49 if parent
is not None:
50 self
.main_window
.set_transient_for(parent
)
52 if hasattr(self
, 'center_on_widget'):
53 (x
, y
) = parent
.get_position()
54 a
= self
.center_on_widget
.allocation
55 (x
, y
) = (x
+ a
.x
, y
+ a
.y
)
56 (w
, h
) = (a
.width
, a
.height
)
57 (pw
, ph
) = self
.main_window
.get_size()
58 self
.main_window
.move(x
+ w
/2 - pw
/2, y
+ h
/2 - ph
/2)
60 def _on_window_state_event_visibility(self
, widget
, event
):
61 if event
.state
& gtk
.gdk
.VISIBILITY_FULLY_OBSCURED
:
62 self
._window
_visible
= False
64 self
._window
_visible
= True
68 def _on_window_state_event_iconified(self
, widget
, event
):
69 if event
.new_window_state
& gtk
.gdk
.WINDOW_STATE_ICONIFIED
:
70 if not self
._window
_iconified
:
71 self
._window
_iconified
= True
74 if self
._window
_iconified
:
75 self
._window
_iconified
= False
80 def is_iconified(self
):
81 return self
._window
_iconified
83 def notification(self
, message
, title
=None, important
=False, widget
=None):
84 util
.idle_add(self
.show_message
, message
, title
, important
, widget
)
86 def get_dialog_parent(self
):
87 """Return a gtk.Window that should be the parent of dialogs"""
88 return self
.main_window
90 def show_message(self
, message
, title
=None, important
=False, widget
=None):
92 dlg
= gtk
.MessageDialog(self
.main_window
, gtk
.DIALOG_MODAL
, gtk
.MESSAGE_INFO
, gtk
.BUTTONS_OK
)
94 dlg
.set_title(str(title
))
95 dlg
.set_markup('<span weight="bold" size="larger">%s</span>\n\n%s' % (title
, message
))
97 dlg
.set_markup('<span weight="bold" size="larger">%s</span>' % (message
))
101 gpodder
.user_extensions
.on_notification_show(title
, message
)
103 def show_confirmation(self
, message
, title
=None):
104 dlg
= gtk
.MessageDialog(self
.main_window
, gtk
.DIALOG_MODAL
, gtk
.MESSAGE_QUESTION
, gtk
.BUTTONS_YES_NO
)
106 dlg
.set_title(str(title
))
107 dlg
.set_markup('<span weight="bold" size="larger">%s</span>\n\n%s' % (title
, message
))
109 dlg
.set_markup('<span weight="bold" size="larger">%s</span>' % (message
))
112 return response
== gtk
.RESPONSE_YES
114 def show_text_edit_dialog(self
, title
, prompt
, text
=None, empty
=False, \
115 is_url
=False, affirmative_text
=gtk
.STOCK_OK
):
116 dialog
= gtk
.Dialog(title
, self
.get_dialog_parent(), \
117 gtk
.DIALOG_MODAL | gtk
.DIALOG_DESTROY_WITH_PARENT
)
119 dialog
.add_button(gtk
.STOCK_CANCEL
, gtk
.RESPONSE_CANCEL
)
120 dialog
.add_button(affirmative_text
, gtk
.RESPONSE_OK
)
122 dialog
.set_has_separator(False)
123 dialog
.set_default_size(300, -1)
124 dialog
.set_default_response(gtk
.RESPONSE_OK
)
126 text_entry
= gtk
.Entry()
127 text_entry
.set_activates_default(True)
129 text_entry
.set_text(text
)
130 text_entry
.select_region(0, -1)
133 def on_text_changed(editable
):
134 can_confirm
= (editable
.get_text() != '')
135 dialog
.set_response_sensitive(gtk
.RESPONSE_OK
, can_confirm
)
136 text_entry
.connect('changed', on_text_changed
)
138 dialog
.set_response_sensitive(gtk
.RESPONSE_OK
, False)
141 hbox
.set_border_width(10)
143 hbox
.pack_start(gtk
.Label(prompt
), False, False)
144 hbox
.pack_start(text_entry
, True, True)
145 dialog
.vbox
.pack_start(hbox
, True, True)
148 response
= dialog
.run()
149 result
= text_entry
.get_text()
152 if response
== gtk
.RESPONSE_OK
:
157 def show_login_dialog(self
, title
, message
, username
=None, password
=None,
158 username_prompt
=None, register_callback
=None, register_text
=None):
159 if username_prompt
is None:
160 username_prompt
= _('Username')
162 if register_text
is None:
163 register_text
= _('New user')
165 dialog
= gtk
.MessageDialog(
167 gtk
.DIALOG_MODAL | gtk
.DIALOG_DESTROY_WITH_PARENT
,
168 gtk
.MESSAGE_QUESTION
,
170 dialog
.add_button(_('Login'), gtk
.RESPONSE_OK
)
171 dialog
.set_image(gtk
.image_new_from_stock(gtk
.STOCK_DIALOG_AUTHENTICATION
, gtk
.ICON_SIZE_DIALOG
))
172 dialog
.set_title(_('Authentication required'))
173 dialog
.set_markup('<span weight="bold" size="larger">' + title
+ '</span>')
174 dialog
.format_secondary_markup(message
)
175 dialog
.set_default_response(gtk
.RESPONSE_OK
)
177 if register_callback
is not None:
178 dialog
.add_button(register_text
, gtk
.RESPONSE_HELP
)
180 username_entry
= gtk
.Entry()
181 password_entry
= gtk
.Entry()
183 username_entry
.connect('activate', lambda w
: password_entry
.grab_focus())
184 password_entry
.set_visibility(False)
185 password_entry
.set_activates_default(True)
187 if username
is not None:
188 username_entry
.set_text(username
)
189 if password
is not None:
190 password_entry
.set_text(password
)
192 table
= gtk
.Table(2, 2)
193 table
.set_row_spacings(6)
194 table
.set_col_spacings(6)
196 username_label
= gtk
.Label()
197 username_label
.set_markup('<b>' + username_prompt
+ ':</b>')
198 username_label
.set_alignment(0.0, 0.5)
199 table
.attach(username_label
, 0, 1, 0, 1, gtk
.FILL
, 0)
200 table
.attach(username_entry
, 1, 2, 0, 1)
202 password_label
= gtk
.Label()
203 password_label
.set_markup('<b>' + _('Password') + ':</b>')
204 password_label
.set_alignment(0.0, 0.5)
205 table
.attach(password_label
, 0, 1, 1, 2, gtk
.FILL
, 0)
206 table
.attach(password_entry
, 1, 2, 1, 2)
208 dialog
.vbox
.pack_end(table
, True, True, 0)
210 response
= dialog
.run()
212 while response
== gtk
.RESPONSE_HELP
:
214 response
= dialog
.run()
216 password_entry
.set_visibility(True)
217 username
= username_entry
.get_text()
218 password
= password_entry
.get_text()
219 success
= (response
== gtk
.RESPONSE_OK
)
223 return (success
, (username
, password
))
225 def show_copy_dialog(self
, src_filename
, dst_filename
=None, dst_directory
=None, title
=_('Select destination')):
226 if dst_filename
is None:
227 dst_filename
= src_filename
229 if dst_directory
is None:
230 dst_directory
= os
.path
.expanduser('~')
232 base
, extension
= os
.path
.splitext(src_filename
)
234 if not dst_filename
.endswith(extension
):
235 dst_filename
+= extension
237 dlg
= gtk
.FileChooserDialog(title
=title
, parent
=self
.main_window
, action
=gtk
.FILE_CHOOSER_ACTION_SAVE
)
238 dlg
.add_button(gtk
.STOCK_CANCEL
, gtk
.RESPONSE_CANCEL
)
239 dlg
.add_button(gtk
.STOCK_SAVE
, gtk
.RESPONSE_OK
)
241 dlg
.set_do_overwrite_confirmation(True)
242 dlg
.set_current_name(os
.path
.basename(dst_filename
))
243 dlg
.set_current_folder(dst_directory
)
246 folder
= dst_directory
247 if dlg
.run() == gtk
.RESPONSE_OK
:
249 dst_filename
= dlg
.get_filename()
250 folder
= dlg
.get_current_folder()
251 if not dst_filename
.endswith(extension
):
252 dst_filename
+= extension
254 shutil
.copyfile(src_filename
, dst_filename
)
257 return (result
, folder
)
259 class TreeViewHelper(object):
260 """Container for gPodder-specific TreeView attributes."""
261 LAST_TOOLTIP
= '_gpodder_last_tooltip'
262 CAN_TOOLTIP
= '_gpodder_can_tooltip'
263 ROLE
= '_gpodder_role'
264 COLUMNS
= '_gpodder_columns'
266 # Enum for the role attribute
267 ROLE_PODCASTS
, ROLE_EPISODES
, ROLE_DOWNLOADS
= range(3)
270 def set(cls
, treeview
, role
):
271 setattr(treeview
, cls
.LAST_TOOLTIP
, None)
272 setattr(treeview
, cls
.CAN_TOOLTIP
, True)
273 setattr(treeview
, cls
.ROLE
, role
)
276 def make_search_equal_func(gpodder_model
):
277 def func(model
, column
, key
, iter):
281 for column
in gpodder_model
.SEARCH_COLUMNS
:
282 if key
in model
.get_value(iter, column
).lower():
288 def register_column(cls
, treeview
, column
):
289 if not hasattr(treeview
, cls
.COLUMNS
):
290 setattr(treeview
, cls
.COLUMNS
, [])
292 columns
= getattr(treeview
, cls
.COLUMNS
)
293 columns
.append(column
)
296 def get_columns(cls
, treeview
):
297 return getattr(treeview
, cls
.COLUMNS
, [])
300 def make_popup_position_func(widget
):
301 def position_func(menu
):
302 x
, y
= widget
.window
.get_origin()