317 lines
12 KiB
Python
317 lines
12 KiB
Python
# -*- coding: utf-8 -*-
|
|
#
|
|
# gPodder - A media aggregator and podcast client
|
|
# Copyright (c) 2005-2018 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/>.
|
|
#
|
|
|
|
import os
|
|
import shutil
|
|
|
|
from gi.repository import Gdk, Gtk
|
|
|
|
import gpodder
|
|
from gpodder import util
|
|
from gpodder.gtkui.base import GtkBuilderWidget
|
|
|
|
_ = gpodder.gettext
|
|
|
|
|
|
def show_message_dialog(parent, message, title=None):
|
|
dlg = Gtk.MessageDialog(parent, Gtk.DialogFlags.MODAL, Gtk.MessageType.INFO, Gtk.ButtonsType.OK)
|
|
if title:
|
|
dlg.set_title(str(title))
|
|
dlg.set_markup('<span weight="bold" size="larger">%s</span>\n\n%s' % (title, message))
|
|
else:
|
|
dlg.set_markup('<span weight="bold" size="larger">%s</span>' % (message))
|
|
# make message copy/pastable
|
|
for lbl in dlg.get_message_area():
|
|
if isinstance(lbl, Gtk.Label):
|
|
lbl.set_selectable(True)
|
|
dlg.run()
|
|
dlg.destroy()
|
|
|
|
|
|
class BuilderWidget(GtkBuilderWidget):
|
|
def __init__(self, parent, **kwargs):
|
|
self._window_iconified = False
|
|
|
|
GtkBuilderWidget.__init__(self, gpodder.ui_folders, gpodder.textdomain, parent, **kwargs)
|
|
|
|
# Enable support for tracking iconified state
|
|
if hasattr(self, 'on_iconify') and hasattr(self, 'on_uniconify'):
|
|
self.main_window.connect('window-state-event',
|
|
self._on_window_state_event_iconified)
|
|
|
|
def _on_window_state_event_iconified(self, widget, event):
|
|
if event.new_window_state & Gdk.WindowState.ICONIFIED:
|
|
if not self._window_iconified:
|
|
self._window_iconified = True
|
|
self.on_iconify()
|
|
else:
|
|
if self._window_iconified:
|
|
self._window_iconified = False
|
|
self.on_uniconify()
|
|
|
|
return False
|
|
|
|
def is_iconified(self):
|
|
return self._window_iconified
|
|
|
|
def notification(self, message, title=None, important=False, widget=None):
|
|
util.idle_add(self.show_message, message, title, important, widget)
|
|
|
|
def get_dialog_parent(self):
|
|
"""Return a Gtk.Window that should be the parent of dialogs"""
|
|
return self.main_window
|
|
|
|
def show_message(self, message, title=None, important=False, widget=None):
|
|
if important:
|
|
show_message_dialog(self.main_window, message, title)
|
|
else:
|
|
gpodder.user_extensions.on_notification_show(title, message)
|
|
|
|
def show_confirmation(self, message, title=None):
|
|
dlg = Gtk.MessageDialog(self.main_window, Gtk.DialogFlags.MODAL, Gtk.MessageType.QUESTION, Gtk.ButtonsType.YES_NO)
|
|
if title:
|
|
dlg.set_title(str(title))
|
|
dlg.set_markup('<span weight="bold" size="larger">%s</span>\n\n%s' % (title, message))
|
|
else:
|
|
dlg.set_markup('<span weight="bold" size="larger">%s</span>' % (message))
|
|
response = dlg.run()
|
|
dlg.destroy()
|
|
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):
|
|
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(affirmative_text, Gtk.ResponseType.OK)
|
|
|
|
dialog.set_default_size(300, -1)
|
|
dialog.set_default_response(Gtk.ResponseType.OK)
|
|
|
|
text_entry = Gtk.Entry()
|
|
text_entry.set_activates_default(True)
|
|
if text is not None:
|
|
text_entry.set_text(text)
|
|
text_entry.select_region(0, -1)
|
|
|
|
if not empty:
|
|
def on_text_changed(editable):
|
|
can_confirm = (editable.get_text() != '')
|
|
dialog.set_response_sensitive(Gtk.ResponseType.OK, can_confirm)
|
|
text_entry.connect('changed', on_text_changed)
|
|
if text is None:
|
|
dialog.set_response_sensitive(Gtk.ResponseType.OK, False)
|
|
|
|
hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
|
|
hbox.set_border_width(10)
|
|
hbox.set_spacing(10)
|
|
hbox.pack_start(Gtk.Label(prompt, True, True, 0), False, False, 0)
|
|
hbox.pack_start(text_entry, True, True, 0)
|
|
dialog.vbox.pack_start(hbox, True, True, 0)
|
|
|
|
dialog.show_all()
|
|
response = dialog.run()
|
|
result = text_entry.get_text()
|
|
dialog.destroy()
|
|
|
|
if response == Gtk.ResponseType.OK:
|
|
return result
|
|
else:
|
|
return None
|
|
|
|
def show_login_dialog(self, title, message, root_url=None, username=None, password=None,
|
|
username_prompt=None, register_callback=None, register_text=None, ask_server=False):
|
|
def toggle_password_visibility(_, entry):
|
|
entry.set_visibility(not entry.get_visibility())
|
|
|
|
if username_prompt is None:
|
|
username_prompt = _('Username')
|
|
|
|
if register_text is None:
|
|
register_text = _('New user')
|
|
|
|
dialog = Gtk.MessageDialog(
|
|
self.main_window,
|
|
Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT,
|
|
Gtk.MessageType.QUESTION,
|
|
Gtk.ButtonsType.CANCEL)
|
|
dialog.add_button(_('Login'), Gtk.ResponseType.OK)
|
|
dialog.set_image(Gtk.Image.new_from_icon_name('dialog-password', Gtk.IconSize.DIALOG))
|
|
dialog.set_title(_('Authentication required'))
|
|
dialog.set_markup('<span weight="bold" size="larger">' + title + '</span>')
|
|
dialog.format_secondary_markup(message)
|
|
dialog.set_default_response(Gtk.ResponseType.OK)
|
|
|
|
if register_callback is not None:
|
|
dialog.add_button(register_text, Gtk.ResponseType.HELP)
|
|
|
|
server_entry = Gtk.Entry()
|
|
server_entry.set_tooltip_text(_('hostname or root URL (e.g. https://gpodder.net)'))
|
|
username_entry = Gtk.Entry()
|
|
password_entry = Gtk.Entry()
|
|
|
|
server_entry.connect('activate', lambda w: username_entry.grab_focus())
|
|
username_entry.connect('activate', lambda w: password_entry.grab_focus())
|
|
password_entry.set_visibility(False)
|
|
password_entry.set_activates_default(True)
|
|
|
|
if root_url is not None:
|
|
server_entry.set_text(root_url)
|
|
if username is not None:
|
|
username_entry.set_text(username)
|
|
if password is not None:
|
|
password_entry.set_text(password)
|
|
|
|
table = Gtk.Table(3, 2)
|
|
table.set_row_spacings(6)
|
|
table.set_col_spacings(6)
|
|
|
|
server_label = Gtk.Label()
|
|
server_label.set_markup('<b>' + _('Server') + ':</b>')
|
|
|
|
username_label = Gtk.Label()
|
|
username_label.set_markup('<b>' + username_prompt + ':</b>')
|
|
|
|
password_label = Gtk.Label()
|
|
password_label.set_markup('<b>' + _('Password') + ':</b>')
|
|
|
|
show_password_label = Gtk.Label()
|
|
show_password = Gtk.CheckButton.new_with_label(_('Show Password'))
|
|
show_password.connect('toggled', toggle_password_visibility, password_entry)
|
|
|
|
label_entries = [(username_label, username_entry),
|
|
(password_label, password_entry),
|
|
(show_password_label, show_password)]
|
|
|
|
if ask_server:
|
|
label_entries.insert(0, (server_label, server_entry))
|
|
|
|
for i, (label, entry) in enumerate(label_entries):
|
|
label.set_alignment(0.0, 0.5)
|
|
table.attach(label, 0, 1, i, i + 1, Gtk.AttachOptions.FILL, 0)
|
|
table.attach(entry, 1, 2, i, i + 1)
|
|
|
|
dialog.vbox.pack_end(table, True, True, 0)
|
|
dialog.show_all()
|
|
username_entry.grab_focus()
|
|
response = dialog.run()
|
|
|
|
while response == Gtk.ResponseType.HELP:
|
|
register_callback()
|
|
response = dialog.run()
|
|
|
|
password_entry.set_visibility(True)
|
|
root_url = server_entry.get_text()
|
|
username = username_entry.get_text()
|
|
password = password_entry.get_text()
|
|
success = (response == Gtk.ResponseType.OK)
|
|
|
|
dialog.destroy()
|
|
|
|
if ask_server:
|
|
return (success, (root_url, username, password))
|
|
else:
|
|
return (success, (username, password))
|
|
|
|
def show_folder_select_dialog(self, initial_directory=None, title=_('Select destination')):
|
|
if initial_directory is None:
|
|
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.set_do_overwrite_confirmation(True)
|
|
dlg.set_current_folder(initial_directory)
|
|
|
|
result = False
|
|
folder = initial_directory
|
|
if dlg.run() == Gtk.ResponseType.OK:
|
|
result = True
|
|
folder = dlg.get_current_folder()
|
|
|
|
dlg.destroy()
|
|
return (result, folder)
|
|
|
|
|
|
class TreeViewHelper(object):
|
|
"""Container for gPodder-specific TreeView attributes."""
|
|
LAST_TOOLTIP = '_gpodder_last_tooltip'
|
|
CAN_TOOLTIP = '_gpodder_can_tooltip'
|
|
ROLE = '_gpodder_role'
|
|
COLUMNS = '_gpodder_columns'
|
|
|
|
# Enum for the role attribute
|
|
ROLE_PODCASTS, ROLE_EPISODES, ROLE_DOWNLOADS = list(range(3))
|
|
|
|
@classmethod
|
|
def set(cls, treeview, role):
|
|
setattr(treeview, cls.LAST_TOOLTIP, None)
|
|
setattr(treeview, cls.CAN_TOOLTIP, True)
|
|
setattr(treeview, cls.ROLE, role)
|
|
|
|
@staticmethod
|
|
def make_search_equal_func(gpodder_model):
|
|
def func(model, column, key, iter):
|
|
if model is None:
|
|
return True
|
|
key = key.lower()
|
|
for column in gpodder_model.SEARCH_COLUMNS:
|
|
if key in model.get_value(iter, column).lower():
|
|
return False
|
|
return True
|
|
return func
|
|
|
|
@classmethod
|
|
def register_column(cls, treeview, column):
|
|
if not hasattr(treeview, cls.COLUMNS):
|
|
setattr(treeview, cls.COLUMNS, [])
|
|
|
|
columns = getattr(treeview, cls.COLUMNS)
|
|
columns.append(column)
|
|
|
|
@classmethod
|
|
def get_columns(cls, treeview):
|
|
return getattr(treeview, cls.COLUMNS, [])
|
|
|
|
@staticmethod
|
|
def make_popup_position_func(widget):
|
|
"""
|
|
:return: suitable function to pass to Gtk.Menu.popup()
|
|
It's used for instance when the popup trigger is the Menu key:
|
|
it will position the menu on top of the selected row even if the mouse is elsewhere
|
|
see http://lazka.github.io/pgi-docs/#Gtk-3.0/classes/Menu.html#Gtk.Menu.popup
|
|
"""
|
|
def position_func(menu, *unused_args):
|
|
_, x, y = widget.get_bin_window().get_origin()
|
|
|
|
# If there's a selection, place the popup menu on top of
|
|
# the first-selected row (otherwise in the top left corner)
|
|
selection = widget.get_selection()
|
|
model, paths = selection.get_selected_rows()
|
|
if paths:
|
|
path = paths[0]
|
|
area = widget.get_cell_area(path, widget.get_column(0))
|
|
x += area.x
|
|
y += area.y
|
|
|
|
return (x, y, True)
|
|
return position_func
|