2013-01-23 17:28:19 +00:00
|
|
|
# Miscellaneous UI functions
|
|
|
|
#
|
2014-04-07 12:38:09 +00:00
|
|
|
# Copyright (C) 2012, 2013 Red Hat, Inc.
|
2013-01-23 17:28:19 +00:00
|
|
|
#
|
|
|
|
# This copyrighted material is made available to anyone wishing to use,
|
|
|
|
# modify, copy, or redistribute it subject to the terms and conditions of
|
|
|
|
# the GNU General Public License v.2, or (at your option) any later version.
|
|
|
|
# This program is distributed in the hope that it will be useful, but WITHOUT
|
|
|
|
# ANY WARRANTY expressed or implied, including the implied warranties 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, write to the
|
|
|
|
# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
|
|
|
# 02110-1301, USA. Any Red Hat trademarks that are incorporated in the
|
|
|
|
# source code or documentation are not subject to the GNU General Public
|
|
|
|
# License and may only be used or replicated with the express permission of
|
|
|
|
# Red Hat, Inc.
|
|
|
|
#
|
|
|
|
# Red Hat Author(s): Chris Lumens <clumens@redhat.com>
|
2014-04-07 12:38:09 +00:00
|
|
|
# Martin Sivak <msivak@redhat.com>
|
|
|
|
# Vratislav Podzimek <vpodzime@redhat.com>
|
2013-01-23 17:28:19 +00:00
|
|
|
#
|
2014-04-07 12:38:09 +00:00
|
|
|
|
|
|
|
from pyanaconda.threads import threadMgr
|
|
|
|
|
2013-01-23 17:28:19 +00:00
|
|
|
from contextlib import contextmanager
|
2014-04-07 12:38:09 +00:00
|
|
|
from gi.repository import Gdk, Gtk, GLib, AnacondaWidgets
|
2013-01-23 17:28:19 +00:00
|
|
|
import Queue
|
2014-04-07 12:38:09 +00:00
|
|
|
import gettext
|
|
|
|
import time
|
2013-01-23 17:28:19 +00:00
|
|
|
|
|
|
|
def gtk_call_once(func, *args):
|
|
|
|
"""Wrapper for GLib.idle_add call that ensures the func is called
|
|
|
|
only once.
|
2014-04-07 12:38:09 +00:00
|
|
|
"""
|
2013-01-23 17:28:19 +00:00
|
|
|
def wrap(args):
|
|
|
|
func(*args)
|
|
|
|
return False
|
|
|
|
|
2014-04-07 12:38:09 +00:00
|
|
|
GLib.idle_add(wrap, args)
|
2013-01-23 17:28:19 +00:00
|
|
|
|
2014-04-07 12:38:09 +00:00
|
|
|
def gtk_action_wait(func):
|
|
|
|
"""Decorator method which ensures every call of the decorated function to be
|
|
|
|
executed in the context of Gtk main loop even if called from a non-main
|
|
|
|
thread and returns the ret value after the decorated method finishes.
|
2013-01-23 17:28:19 +00:00
|
|
|
"""
|
2014-04-07 12:38:09 +00:00
|
|
|
|
2013-01-23 17:28:19 +00:00
|
|
|
queue = Queue.Queue()
|
|
|
|
|
|
|
|
def _idle_method(q_args):
|
|
|
|
"""This method contains the code for the main loop to execute.
|
|
|
|
"""
|
|
|
|
queue, args = q_args
|
|
|
|
ret = func(*args)
|
|
|
|
queue.put(ret)
|
|
|
|
return False
|
|
|
|
|
|
|
|
def _call_method(*args):
|
2014-04-07 12:38:09 +00:00
|
|
|
"""The new body for the decorated method. If needed, it uses closure
|
|
|
|
bound queue variable which is valid until the reference to this
|
|
|
|
method is destroyed."""
|
|
|
|
if threadMgr.in_main_thread():
|
|
|
|
# nothing special has to be done in the main thread
|
|
|
|
return func(*args)
|
|
|
|
|
2013-01-23 17:28:19 +00:00
|
|
|
GLib.idle_add(_idle_method, (queue, args))
|
|
|
|
return queue.get()
|
|
|
|
|
|
|
|
return _call_method
|
|
|
|
|
|
|
|
|
2014-04-07 12:38:09 +00:00
|
|
|
def gtk_action_nowait(func):
|
|
|
|
"""Decorator method which ensures every call of the decorated function to be
|
|
|
|
executed in the context of Gtk main loop even if called from a non-main
|
|
|
|
thread. The new method does not wait for the callback to finish.
|
2013-01-23 17:28:19 +00:00
|
|
|
"""
|
|
|
|
|
|
|
|
def _idle_method(args):
|
|
|
|
"""This method contains the code for the main loop to execute.
|
|
|
|
"""
|
2014-04-07 12:38:09 +00:00
|
|
|
func(*args)
|
2013-01-23 17:28:19 +00:00
|
|
|
return False
|
|
|
|
|
|
|
|
def _call_method(*args):
|
|
|
|
"""The new body for the decorated method.
|
|
|
|
"""
|
2014-04-07 12:38:09 +00:00
|
|
|
if threadMgr.in_main_thread():
|
|
|
|
# nothing special has to be done in the main thread
|
|
|
|
func(*args)
|
|
|
|
return
|
|
|
|
|
2013-01-23 17:28:19 +00:00
|
|
|
GLib.idle_add(_idle_method, args)
|
|
|
|
|
|
|
|
return _call_method
|
|
|
|
|
|
|
|
|
2014-04-07 12:38:09 +00:00
|
|
|
def timed_action(delay=300, threshold=750, busy_cursor=True):
|
|
|
|
"""
|
|
|
|
Function returning decorator for decorating often repeated actions that need
|
|
|
|
to happen in the main loop (entry/slider change callbacks, typically), but
|
|
|
|
that may take a long time causing the GUI freeze for a noticeable time.
|
|
|
|
|
|
|
|
:param delay: number of milliseconds to wait for another invocation of the
|
|
|
|
decorated function before it is actually called
|
|
|
|
:type delay: int
|
|
|
|
:param threshold: upper bound (in milliseconds) to wait for the decorated
|
|
|
|
function to be called from the first/last time
|
|
|
|
:type threshold: int
|
|
|
|
:param busy_cursor: whether the cursor should be made busy or not in the
|
|
|
|
meantime of the decorated function being invocated from
|
|
|
|
outside and it actually being called
|
|
|
|
:type busy_cursor: bool
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
class TimedAction(object):
|
|
|
|
"""Class making the timing work."""
|
|
|
|
|
|
|
|
def __init__(self, func):
|
|
|
|
self._func = func
|
|
|
|
self._last_start = None
|
|
|
|
self._timer_id = None
|
|
|
|
|
|
|
|
def _run_once_one_arg(self, (args, kwargs)):
|
|
|
|
# run the function and clear stored values
|
|
|
|
self._func(*args, **kwargs)
|
|
|
|
self._last_start = None
|
|
|
|
self._timer_id = None
|
|
|
|
if busy_cursor:
|
|
|
|
unbusyCursor()
|
|
|
|
|
|
|
|
# function run, no need to schedule it again (return True would do)
|
|
|
|
return False
|
|
|
|
|
|
|
|
def run_func(self, *args, **kwargs):
|
|
|
|
# get timestamps from the first or/and current run
|
|
|
|
self._last_start = self._last_start or time.time()
|
|
|
|
tstamp = time.time()
|
|
|
|
|
|
|
|
if self._timer_id:
|
|
|
|
# remove the old timer scheduling the function
|
|
|
|
GLib.source_remove(self._timer_id)
|
|
|
|
self._timer_id = None
|
|
|
|
|
|
|
|
# are we over the threshold?
|
|
|
|
if (tstamp - self._last_start) * 1000 > threshold:
|
|
|
|
# over threshold, run the function right now and clear the
|
|
|
|
# timestamp
|
|
|
|
self._func(*args, **kwargs)
|
|
|
|
self._last_start = None
|
|
|
|
|
|
|
|
# schedule the function to be run later to allow additional changes
|
|
|
|
# in the meantime
|
|
|
|
if busy_cursor:
|
|
|
|
busyCursor()
|
|
|
|
self._timer_id = GLib.timeout_add(delay, self._run_once_one_arg,
|
|
|
|
(args, kwargs))
|
|
|
|
|
|
|
|
def decorator(func):
|
|
|
|
"""
|
|
|
|
Decorator replacing the function with its timed version using an
|
|
|
|
instance of the TimedAction class.
|
|
|
|
|
|
|
|
:param func: the decorated function
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
ta = TimedAction(func)
|
|
|
|
|
|
|
|
def inner_func(*args, **kwargs):
|
|
|
|
ta.run_func(*args, **kwargs)
|
|
|
|
|
|
|
|
return inner_func
|
|
|
|
|
|
|
|
return decorator
|
|
|
|
|
|
|
|
def busyCursor():
|
|
|
|
window = Gdk.get_default_root_window()
|
|
|
|
window.set_cursor(Gdk.Cursor(Gdk.CursorType.WATCH))
|
|
|
|
|
|
|
|
def unbusyCursor():
|
|
|
|
window = Gdk.get_default_root_window()
|
|
|
|
window.set_cursor(Gdk.Cursor(Gdk.CursorType.ARROW))
|
|
|
|
|
2013-01-23 17:28:19 +00:00
|
|
|
@contextmanager
|
|
|
|
def enlightbox(mainWindow, dialog):
|
2014-04-07 12:38:09 +00:00
|
|
|
# importing globally would cause a circular dependency
|
|
|
|
from pyanaconda.ui.gui import ANACONDA_WINDOW_GROUP
|
|
|
|
|
|
|
|
lightbox = AnacondaWidgets.Lightbox(parent_window=mainWindow)
|
|
|
|
ANACONDA_WINDOW_GROUP.add_window(lightbox)
|
|
|
|
dialog.set_position(Gtk.WindowPosition.CENTER_ALWAYS)
|
2013-01-23 17:28:19 +00:00
|
|
|
dialog.set_transient_for(lightbox)
|
|
|
|
yield
|
|
|
|
lightbox.destroy()
|
|
|
|
|
2014-04-07 12:38:09 +00:00
|
|
|
def ignoreEscape(dlg):
|
|
|
|
"""Prevent a dialog from accepting the escape keybinding, which emits a
|
|
|
|
close signal and will cause the dialog to close with some return value
|
|
|
|
we are likely not expecting. Instead, this method will cause the
|
|
|
|
escape key to do nothing for the given GtkDialog.
|
|
|
|
"""
|
|
|
|
provider = Gtk.CssProvider()
|
|
|
|
provider.load_from_data("@binding-set IgnoreEscape {"
|
|
|
|
" unbind 'Escape';"
|
|
|
|
"}"
|
|
|
|
"GtkDialog { gtk-key-bindings: IgnoreEscape }")
|
|
|
|
|
|
|
|
context = dlg.get_style_context()
|
|
|
|
context.add_provider(provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)
|
|
|
|
|
2013-01-23 17:28:19 +00:00
|
|
|
def setViewportBackground(vp, color="@theme_bg_color"):
|
|
|
|
"""Set the background color of the GtkViewport vp to be the same as the
|
|
|
|
overall UI background. This should not be called for every viewport,
|
|
|
|
as that will affect things like TreeViews as well.
|
|
|
|
"""
|
|
|
|
|
|
|
|
provider = Gtk.CssProvider()
|
|
|
|
provider.load_from_data("GtkViewport { background-color: %s }" % color)
|
|
|
|
context = vp.get_style_context()
|
|
|
|
context.add_provider(provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)
|
2014-04-07 12:38:09 +00:00
|
|
|
|
|
|
|
def fancy_set_sensitive(widget, value):
|
|
|
|
"""Set the sensitivity of a widget, and then set the sensitivity of
|
|
|
|
all widgets it is a mnemonic widget for. This has the effect of
|
|
|
|
marking both an entry and its label as sensitive/insensitive, for
|
|
|
|
instance.
|
|
|
|
"""
|
|
|
|
widget.set_sensitive(value)
|
|
|
|
for w in widget.list_mnemonic_labels():
|
|
|
|
w.set_sensitive(value)
|
|
|
|
|
|
|
|
def really_hide(widget):
|
|
|
|
"""Some widgets need to be both hidden, and have no_show_all set on them
|
|
|
|
to prevent them from being shown later when the screen is redrawn.
|
|
|
|
This method takes care of that.
|
|
|
|
"""
|
|
|
|
widget.set_no_show_all(True)
|
|
|
|
widget.hide()
|
|
|
|
|
|
|
|
def really_show(widget):
|
|
|
|
"""Some widgets need to have no_show_all unset before they can also be
|
|
|
|
shown, so they are displayed later when the screen is redrawn. This
|
|
|
|
method takes care of that.
|
|
|
|
"""
|
|
|
|
widget.set_no_show_all(False)
|
|
|
|
widget.show()
|
|
|
|
|
|
|
|
def set_treeview_selection(treeview, item, col=0):
|
|
|
|
"""
|
|
|
|
Select the given item in the given treeview and scroll to it.
|
|
|
|
|
|
|
|
:param treeview: treeview to select and item in
|
|
|
|
:type treeview: GtkTreeView
|
|
|
|
:param item: item to be selected
|
|
|
|
:type item: str
|
|
|
|
:param col: column to search for the item in
|
|
|
|
:type col: int
|
|
|
|
:return: selected iterator or None if item was not found
|
|
|
|
:rtype: GtkTreeIter or None
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
model = treeview.get_model()
|
|
|
|
itr = model.get_iter_first()
|
|
|
|
while itr and not model[itr][col] == item:
|
|
|
|
itr = model.iter_next(itr)
|
|
|
|
|
|
|
|
if not itr:
|
|
|
|
# item not found, cannot be selected
|
|
|
|
return None
|
|
|
|
|
|
|
|
# otherwise select the item and scroll to it
|
|
|
|
selection = treeview.get_selection()
|
|
|
|
selection.select_iter(itr)
|
|
|
|
path = model.get_path(itr)
|
|
|
|
|
|
|
|
# row_align=0.5 tells GTK to move the cell to the middle of the
|
|
|
|
# treeview viewport (0.0 should align it with the top, 1.0 with bottom)
|
|
|
|
# If the cell is the uppermost one, it should align it with the top
|
|
|
|
# of the viewport.
|
|
|
|
#
|
|
|
|
# Unfortunately, this does not work as expected due to a bug in GTK.
|
|
|
|
# (see rhbz#970048)
|
|
|
|
treeview.scroll_to_cell(path, use_align=True, row_align=0.5)
|
|
|
|
|
|
|
|
return itr
|
|
|
|
|
|
|
|
def get_default_widget_direction():
|
|
|
|
"""
|
|
|
|
Function to get default widget direction (RTL/LTR) for the current language
|
|
|
|
configuration.
|
|
|
|
|
|
|
|
XXX: this should be provided by the Gtk itself (#1008821)
|
|
|
|
|
|
|
|
:return: either Gtk.TextDirection.LTR or Gtk.TextDirection.RTL
|
|
|
|
:rtype: GtkTextDirection
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
# this is quite a hack, but it's exactly the same check Gtk uses internally
|
|
|
|
xlated = gettext.ldgettext("gtk30", "default:LTR")
|
|
|
|
if xlated == "default:LTR":
|
|
|
|
return Gtk.TextDirection.LTR
|
|
|
|
else:
|
|
|
|
return Gtk.TextDirection.RTL
|
|
|
|
|
|
|
|
def setup_gtk_direction():
|
|
|
|
"""
|
|
|
|
Set the right direction (RTL/LTR) of the Gtk widget's and their layout based
|
|
|
|
on the current language configuration.
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
Gtk.Widget.set_default_direction(get_default_widget_direction())
|