2009-08-24 15:31:25 +02:00
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
#
|
|
|
|
# gPodder - A media aggregator and podcast client
|
2018-01-28 19:39:53 +01:00
|
|
|
# Copyright (c) 2005-2018 The gPodder Team
|
2009-08-24 15:31:25 +02:00
|
|
|
#
|
|
|
|
# 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.gtkui.config -- Config object with GTK+ support (2009-08-24)
|
|
|
|
#
|
|
|
|
|
2022-08-23 07:49:11 +02:00
|
|
|
import logging
|
|
|
|
|
2019-02-02 18:35:19 +01:00
|
|
|
import gi # isort:skip
|
|
|
|
gi.require_version('Gdk', '3.0') # isort:skip
|
|
|
|
gi.require_version('Gtk', '3.0') # isort:skip
|
2018-07-24 11:08:10 +02:00
|
|
|
from gi.repository import Gdk, Gtk, Pango
|
2009-08-24 15:31:25 +02:00
|
|
|
|
|
|
|
import gpodder
|
2018-07-24 11:08:10 +02:00
|
|
|
from gpodder import config, util
|
2009-08-24 15:31:25 +02:00
|
|
|
|
2022-08-23 07:49:11 +02:00
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
2009-08-24 15:31:25 +02:00
|
|
|
_ = gpodder.gettext
|
|
|
|
|
2018-02-11 00:22:00 +01:00
|
|
|
|
2016-09-25 14:31:58 +02:00
|
|
|
class ConfigModel(Gtk.ListStore):
|
2009-08-24 15:31:25 +02:00
|
|
|
C_NAME, C_TYPE_TEXT, C_VALUE_TEXT, C_TYPE, C_EDITABLE, C_FONT_STYLE, \
|
2016-11-21 23:13:46 +01:00
|
|
|
C_IS_BOOLEAN, C_BOOLEAN_VALUE = list(range(8))
|
2009-08-24 15:31:25 +02:00
|
|
|
|
|
|
|
def __init__(self, config):
|
2016-09-25 14:31:58 +02:00
|
|
|
Gtk.ListStore.__init__(self, str, str, str, object, bool, int, bool, bool)
|
2009-08-24 15:31:25 +02:00
|
|
|
|
|
|
|
self._config = config
|
|
|
|
self._fill_model()
|
|
|
|
|
|
|
|
self._config.add_observer(self._on_update)
|
|
|
|
|
|
|
|
def _type_as_string(self, type):
|
|
|
|
if type == int:
|
|
|
|
return _('Integer')
|
|
|
|
elif type == float:
|
|
|
|
return _('Float')
|
|
|
|
elif type == bool:
|
|
|
|
return _('Boolean')
|
|
|
|
else:
|
|
|
|
return _('String')
|
|
|
|
|
|
|
|
def _fill_model(self):
|
|
|
|
self.clear()
|
2012-02-21 00:12:15 +01:00
|
|
|
for key in sorted(self._config.all_keys()):
|
|
|
|
# Ignore Gtk window state data (position, size, ...)
|
|
|
|
if key.startswith('ui.gtk.state.'):
|
2011-04-11 11:12:11 +02:00
|
|
|
continue
|
|
|
|
|
2012-02-21 00:12:15 +01:00
|
|
|
value = self._config._lookup(key)
|
|
|
|
fieldtype = type(value)
|
2009-08-24 15:31:25 +02:00
|
|
|
|
2016-09-25 14:31:58 +02:00
|
|
|
style = Pango.Style.NORMAL
|
2018-05-16 18:17:52 +02:00
|
|
|
# if value == default:
|
|
|
|
# style = Pango.Style.NORMAL
|
|
|
|
# else:
|
|
|
|
# style = Pango.Style.ITALIC
|
2009-08-24 15:31:25 +02:00
|
|
|
|
2012-02-21 00:12:15 +01:00
|
|
|
self.append((key, self._type_as_string(fieldtype),
|
2012-02-21 10:22:49 +01:00
|
|
|
config.config_value_to_string(value),
|
|
|
|
fieldtype, fieldtype is not bool, style,
|
2009-08-24 15:31:25 +02:00
|
|
|
fieldtype is bool, bool(value)))
|
|
|
|
|
|
|
|
def _on_update(self, name, old_value, new_value):
|
|
|
|
for row in self:
|
|
|
|
if row[self.C_NAME] == name:
|
2016-09-25 14:31:58 +02:00
|
|
|
style = Pango.Style.NORMAL
|
2018-05-16 18:17:52 +02:00
|
|
|
# if new_value == self._config.Settings[name]:
|
|
|
|
# style = Pango.Style.NORMAL
|
|
|
|
# else:
|
|
|
|
# style = Pango.Style.ITALIC
|
2012-02-21 10:22:49 +01:00
|
|
|
new_value_text = config.config_value_to_string(new_value)
|
2018-05-08 10:43:56 +02:00
|
|
|
self.set(row.iter,
|
2012-02-21 10:22:49 +01:00
|
|
|
self.C_VALUE_TEXT, new_value_text,
|
|
|
|
self.C_BOOLEAN_VALUE, bool(new_value),
|
2009-08-24 15:31:25 +02:00
|
|
|
self.C_FONT_STYLE, style)
|
|
|
|
break
|
|
|
|
|
|
|
|
def stop_observing(self):
|
|
|
|
self._config.remove_observer(self._on_update)
|
|
|
|
|
2018-02-11 00:22:00 +01:00
|
|
|
|
2009-08-24 15:31:25 +02:00
|
|
|
class UIConfig(config.Config):
|
|
|
|
def __init__(self, filename='gpodder.conf'):
|
|
|
|
config.Config.__init__(self, filename)
|
|
|
|
self.__ignore_window_events = False
|
|
|
|
|
|
|
|
def connect_gtk_editable(self, name, editable):
|
|
|
|
editable.delete_text(0, -1)
|
|
|
|
editable.insert_text(str(getattr(self, name)))
|
|
|
|
|
|
|
|
def _editable_changed(editable):
|
|
|
|
setattr(self, name, editable.get_chars(0, -1))
|
|
|
|
editable.connect('changed', _editable_changed)
|
|
|
|
|
2018-04-29 15:59:22 +02:00
|
|
|
def connect_gtk_spinbutton(self, name, spinbutton, forced_upper=None):
|
|
|
|
"""
|
|
|
|
bind a Gtk.SpinButton to a configuration entry.
|
|
|
|
|
|
|
|
It's now possible to specify an upper value (forced_upper).
|
|
|
|
It's not done automatically (always look for name + '_max') because it's
|
|
|
|
used only once. If it becomes commonplace, better make it automatic.
|
|
|
|
|
2022-12-08 22:49:33 +01:00
|
|
|
:param str name: configuration key (e.g. 'limit.downloads.concurrent')
|
2018-04-29 15:59:22 +02:00
|
|
|
:param Gtk.SpinButton spinbutton: button to bind to config
|
|
|
|
:param float forced_upper: forced upper limit on spinbutton.
|
|
|
|
Overrides value in .ui to be consistent with code
|
|
|
|
"""
|
2016-11-19 22:50:52 +01:00
|
|
|
current_value = getattr(self, name)
|
|
|
|
|
|
|
|
adjustment = spinbutton.get_adjustment()
|
2018-04-29 15:59:22 +02:00
|
|
|
if forced_upper is not None:
|
|
|
|
adjustment.set_upper(forced_upper)
|
2016-11-19 22:50:52 +01:00
|
|
|
if current_value > adjustment.get_upper():
|
|
|
|
adjustment.set_upper(current_value)
|
|
|
|
|
|
|
|
spinbutton.set_value(current_value)
|
2009-08-24 15:31:25 +02:00
|
|
|
|
|
|
|
def _spinbutton_changed(spinbutton):
|
|
|
|
setattr(self, name, spinbutton.get_value())
|
|
|
|
spinbutton.connect('value-changed', _spinbutton_changed)
|
|
|
|
|
|
|
|
def connect_gtk_paned(self, name, paned):
|
|
|
|
paned.set_position(getattr(self, name))
|
|
|
|
paned_child = paned.get_child1()
|
|
|
|
|
|
|
|
def _child_size_allocate(x, y):
|
|
|
|
setattr(self, name, paned.get_position())
|
|
|
|
paned_child.connect('size-allocate', _child_size_allocate)
|
|
|
|
|
|
|
|
def connect_gtk_togglebutton(self, name, togglebutton):
|
|
|
|
togglebutton.set_active(getattr(self, name))
|
|
|
|
|
|
|
|
def _togglebutton_toggled(togglebutton):
|
|
|
|
setattr(self, name, togglebutton.get_active())
|
|
|
|
togglebutton.connect('toggled', _togglebutton_toggled)
|
|
|
|
|
|
|
|
def connect_gtk_window(self, window, config_prefix, show_window=False):
|
2012-01-03 23:59:19 +01:00
|
|
|
cfg = getattr(self.ui.gtk.state, config_prefix)
|
2009-08-24 15:31:25 +02:00
|
|
|
|
2013-10-03 02:43:34 +02:00
|
|
|
if gpodder.ui.win32:
|
2018-05-02 10:58:18 +02:00
|
|
|
window.set_gravity(Gdk.Gravity.STATIC)
|
2013-10-03 02:43:34 +02:00
|
|
|
|
2022-08-23 07:49:11 +02:00
|
|
|
if -1 not in (cfg.x, cfg.y, cfg.width, cfg.height):
|
|
|
|
# get screen resolution
|
|
|
|
screen = Gdk.Screen.get_default()
|
|
|
|
screen_width = 0
|
|
|
|
screen_height = 0
|
|
|
|
for i in range(0, screen.get_n_monitors()):
|
|
|
|
monitor = screen.get_monitor_geometry(i)
|
|
|
|
screen_width += monitor.width
|
|
|
|
screen_height += monitor.height
|
|
|
|
logger.debug('Screen %d x %d' % (screen_width, screen_height))
|
|
|
|
# reset window position if more than 50% is off-screen
|
|
|
|
half_width = cfg.width / 2
|
|
|
|
half_height = cfg.height / 2
|
|
|
|
if (cfg.x + cfg.width - half_width) < 0 or (cfg.y + cfg.height - half_height) < 0 \
|
|
|
|
or cfg.x > (screen_width - half_width) or cfg.y > (screen_height - half_height):
|
|
|
|
logger.warning('"%s" window was off-screen at (%d, %d), resetting to default position' % (config_prefix, cfg.x, cfg.y))
|
|
|
|
cfg.x = -1
|
|
|
|
cfg.y = -1
|
|
|
|
|
2019-11-18 21:50:18 +01:00
|
|
|
if cfg.width != -1 and cfg.height != -1:
|
|
|
|
window.resize(cfg.width, cfg.height)
|
2021-04-15 08:11:26 +02:00
|
|
|
|
|
|
|
# Not all platforms can natively restore position, gPodder must handle it.
|
|
|
|
# https://github.com/gpodder/gpodder/pull/933#issuecomment-818039693
|
|
|
|
if cfg.x == -1 or cfg.y == -1:
|
|
|
|
window.set_position(Gtk.WindowPosition.CENTER_ON_PARENT)
|
2023-03-12 00:10:27 +01:00
|
|
|
else:
|
2021-04-15 08:11:26 +02:00
|
|
|
window.move(cfg.x, cfg.y)
|
2021-08-28 10:59:46 +02:00
|
|
|
# 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.
|
2023-03-12 00:10:27 +01:00
|
|
|
# The first move reduces chance of window jumping,
|
|
|
|
# and gives the window manager a position to unmaximize to.
|
|
|
|
if not cfg.maximized:
|
|
|
|
util.idle_add(window.move, cfg.x, cfg.y)
|
2012-01-03 23:59:19 +01:00
|
|
|
|
|
|
|
# Ignore events while we're connecting to the window
|
|
|
|
self.__ignore_window_events = True
|
|
|
|
|
2020-12-25 22:44:43 +01:00
|
|
|
# Get window state, correct size comes from window.get_size(),
|
|
|
|
# see https://developer.gnome.org/SaveWindowState/
|
2012-01-03 23:59:19 +01:00
|
|
|
def _receive_configure_event(widget, event):
|
2023-03-12 04:52:05 +01:00
|
|
|
if not self.__ignore_window_events:
|
|
|
|
# TODO: The maximize event might arrive after the configure event.
|
|
|
|
# This causes the maximized size to be saved, and restoring the
|
|
|
|
# window will not save its smaller size. Delaying the save with
|
|
|
|
# idle_add() is not enough time for the state event to arrive.
|
|
|
|
if not bool(event.window.get_state() & Gdk.WindowState.MAXIMIZED):
|
|
|
|
x_pos, y_pos = widget.get_position()
|
|
|
|
width_size, height_size = widget.get_size()
|
|
|
|
cfg.x = x_pos
|
|
|
|
cfg.y = y_pos
|
|
|
|
cfg.width = width_size
|
|
|
|
cfg.height = height_size
|
2012-01-03 23:59:19 +01:00
|
|
|
|
|
|
|
window.connect('configure-event', _receive_configure_event)
|
|
|
|
|
|
|
|
def _receive_window_state(widget, event):
|
2021-07-17 08:56:02 +02:00
|
|
|
new_value = bool(event.new_window_state & Gdk.WindowState.MAXIMIZED)
|
|
|
|
cfg.maximized = new_value
|
2012-01-03 23:59:19 +01:00
|
|
|
|
|
|
|
window.connect('window-state-event', _receive_window_state)
|
|
|
|
|
|
|
|
# After the window has been set up, we enable events again
|
|
|
|
def _enable_window_events():
|
|
|
|
self.__ignore_window_events = False
|
|
|
|
util.idle_add(_enable_window_events)
|
|
|
|
|
|
|
|
if show_window:
|
|
|
|
window.show()
|
|
|
|
if cfg.maximized:
|
|
|
|
window.maximize()
|