Refactor _receive_configure_event().
[gpodder.git] / src / gpodder / gtkui / config.py
blob4e156bea8e1aa3acacc228503f0fb0a8debb0ff9
1 # -*- coding: utf-8 -*-
3 # gPodder - A media aggregator and podcast client
4 # Copyright (c) 2005-2018 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/>.
22 # gpodder.gtkui.config -- Config object with GTK+ support (2009-08-24)
25 import logging
27 import gi # isort:skip
28 gi.require_version('Gdk', '3.0') # isort:skip
29 gi.require_version('Gtk', '3.0') # isort:skip
30 from gi.repository import Gdk, Gtk, Pango
32 import gpodder
33 from gpodder import config, util
35 logger = logging.getLogger(__name__)
37 _ = gpodder.gettext
40 class ConfigModel(Gtk.ListStore):
41 C_NAME, C_TYPE_TEXT, C_VALUE_TEXT, C_TYPE, C_EDITABLE, C_FONT_STYLE, \
42 C_IS_BOOLEAN, C_BOOLEAN_VALUE = list(range(8))
44 def __init__(self, config):
45 Gtk.ListStore.__init__(self, str, str, str, object, bool, int, bool, bool)
47 self._config = config
48 self._fill_model()
50 self._config.add_observer(self._on_update)
52 def _type_as_string(self, type):
53 if type == int:
54 return _('Integer')
55 elif type == float:
56 return _('Float')
57 elif type == bool:
58 return _('Boolean')
59 else:
60 return _('String')
62 def _fill_model(self):
63 self.clear()
64 for key in sorted(self._config.all_keys()):
65 # Ignore Gtk window state data (position, size, ...)
66 if key.startswith('ui.gtk.state.'):
67 continue
69 value = self._config._lookup(key)
70 fieldtype = type(value)
72 style = Pango.Style.NORMAL
73 # if value == default:
74 # style = Pango.Style.NORMAL
75 # else:
76 # style = Pango.Style.ITALIC
78 self.append((key, self._type_as_string(fieldtype),
79 config.config_value_to_string(value),
80 fieldtype, fieldtype is not bool, style,
81 fieldtype is bool, bool(value)))
83 def _on_update(self, name, old_value, new_value):
84 for row in self:
85 if row[self.C_NAME] == name:
86 style = Pango.Style.NORMAL
87 # if new_value == self._config.Settings[name]:
88 # style = Pango.Style.NORMAL
89 # else:
90 # style = Pango.Style.ITALIC
91 new_value_text = config.config_value_to_string(new_value)
92 self.set(row.iter,
93 self.C_VALUE_TEXT, new_value_text,
94 self.C_BOOLEAN_VALUE, bool(new_value),
95 self.C_FONT_STYLE, style)
96 break
98 def stop_observing(self):
99 self._config.remove_observer(self._on_update)
102 class UIConfig(config.Config):
103 def __init__(self, filename='gpodder.conf'):
104 config.Config.__init__(self, filename)
105 self.__ignore_window_events = False
107 def connect_gtk_editable(self, name, editable):
108 editable.delete_text(0, -1)
109 editable.insert_text(str(getattr(self, name)))
111 def _editable_changed(editable):
112 setattr(self, name, editable.get_chars(0, -1))
113 editable.connect('changed', _editable_changed)
115 def connect_gtk_spinbutton(self, name, spinbutton, forced_upper=None):
117 bind a Gtk.SpinButton to a configuration entry.
119 It's now possible to specify an upper value (forced_upper).
120 It's not done automatically (always look for name + '_max') because it's
121 used only once. If it becomes commonplace, better make it automatic.
123 :param str name: configuration key (e.g. 'limit.downloads.concurrent')
124 :param Gtk.SpinButton spinbutton: button to bind to config
125 :param float forced_upper: forced upper limit on spinbutton.
126 Overrides value in .ui to be consistent with code
128 current_value = getattr(self, name)
130 adjustment = spinbutton.get_adjustment()
131 if forced_upper is not None:
132 adjustment.set_upper(forced_upper)
133 if current_value > adjustment.get_upper():
134 adjustment.set_upper(current_value)
136 spinbutton.set_value(current_value)
138 def _spinbutton_changed(spinbutton):
139 setattr(self, name, spinbutton.get_value())
140 spinbutton.connect('value-changed', _spinbutton_changed)
142 def connect_gtk_paned(self, name, paned):
143 paned.set_position(getattr(self, name))
144 paned_child = paned.get_child1()
146 def _child_size_allocate(x, y):
147 setattr(self, name, paned.get_position())
148 paned_child.connect('size-allocate', _child_size_allocate)
150 def connect_gtk_togglebutton(self, name, togglebutton):
151 togglebutton.set_active(getattr(self, name))
153 def _togglebutton_toggled(togglebutton):
154 setattr(self, name, togglebutton.get_active())
155 togglebutton.connect('toggled', _togglebutton_toggled)
157 def connect_gtk_window(self, window, config_prefix, show_window=False):
158 cfg = getattr(self.ui.gtk.state, config_prefix)
160 if gpodder.ui.win32:
161 window.set_gravity(Gdk.Gravity.STATIC)
163 if -1 not in (cfg.x, cfg.y, cfg.width, cfg.height):
164 # get screen resolution
165 screen = Gdk.Screen.get_default()
166 screen_width = 0
167 screen_height = 0
168 for i in range(0, screen.get_n_monitors()):
169 monitor = screen.get_monitor_geometry(i)
170 screen_width += monitor.width
171 screen_height += monitor.height
172 logger.debug('Screen %d x %d' % (screen_width, screen_height))
173 # reset window position if more than 50% is off-screen
174 half_width = cfg.width / 2
175 half_height = cfg.height / 2
176 if (cfg.x + cfg.width - half_width) < 0 or (cfg.y + cfg.height - half_height) < 0 \
177 or cfg.x > (screen_width - half_width) or cfg.y > (screen_height - half_height):
178 logger.warning('"%s" window was off-screen at (%d, %d), resetting to default position' % (config_prefix, cfg.x, cfg.y))
179 cfg.x = -1
180 cfg.y = -1
182 if cfg.width != -1 and cfg.height != -1:
183 window.resize(cfg.width, cfg.height)
185 # Not all platforms can natively restore position, gPodder must handle it.
186 # https://github.com/gpodder/gpodder/pull/933#issuecomment-818039693
187 if cfg.x == -1 or cfg.y == -1:
188 window.set_position(Gtk.WindowPosition.CENTER_ON_PARENT)
189 else:
190 window.move(cfg.x, cfg.y)
191 # From Gtk docs: most window managers ignore requests for initial window
192 # positions (instead using a user-defined placement algorithm) and honor
193 # requests after the window has already been shown.
194 # Move it a second time after the window has been shown.
195 # The first move reduces chance of window jumping,
196 # and gives the window manager a position to unmaximize to.
197 if not cfg.maximized:
198 util.idle_add(window.move, cfg.x, cfg.y)
200 # Ignore events while we're connecting to the window
201 self.__ignore_window_events = True
203 # Get window state, correct size comes from window.get_size(),
204 # see https://developer.gnome.org/SaveWindowState/
205 def _receive_configure_event(widget, event):
206 if not self.__ignore_window_events:
207 # TODO: The maximize event might arrive after the configure event.
208 # This causes the maximized size to be saved, and restoring the
209 # window will not save its smaller size. Delaying the save with
210 # idle_add() is not enough time for the state event to arrive.
211 if not bool(event.window.get_state() & Gdk.WindowState.MAXIMIZED):
212 x_pos, y_pos = widget.get_position()
213 width_size, height_size = widget.get_size()
214 cfg.x = x_pos
215 cfg.y = y_pos
216 cfg.width = width_size
217 cfg.height = height_size
219 window.connect('configure-event', _receive_configure_event)
221 def _receive_window_state(widget, event):
222 new_value = bool(event.new_window_state & Gdk.WindowState.MAXIMIZED)
223 cfg.maximized = new_value
225 window.connect('window-state-event', _receive_window_state)
227 # After the window has been set up, we enable events again
228 def _enable_window_events():
229 self.__ignore_window_events = False
230 util.idle_add(_enable_window_events)
232 if show_window:
233 window.show()
234 if cfg.maximized:
235 window.maximize()