Combine the sidebar and bottombar functionality into a single class
svn: r16412
This commit is contained in:
parent
92a77413a0
commit
9fe3d1fb8b
@ -79,12 +79,12 @@ src/gen/plug/report/utils.py
|
|||||||
src/gen/proxy/private.py
|
src/gen/proxy/private.py
|
||||||
|
|
||||||
# gui - GUI code
|
# gui - GUI code
|
||||||
src/gui/bottombar.py
|
|
||||||
src/gui/columnorder.py
|
src/gui/columnorder.py
|
||||||
src/gui/configure.py
|
src/gui/configure.py
|
||||||
src/gui/dbloader.py
|
src/gui/dbloader.py
|
||||||
src/gui/dbman.py
|
src/gui/dbman.py
|
||||||
src/gui/filtereditor.py
|
src/gui/filtereditor.py
|
||||||
|
src/gui/grampsbar.py
|
||||||
src/gui/grampsgui.py
|
src/gui/grampsgui.py
|
||||||
src/gui/makefilter.py
|
src/gui/makefilter.py
|
||||||
src/gui/utils.py
|
src/gui/utils.py
|
||||||
|
@ -211,7 +211,6 @@ src/gui/__init__.py
|
|||||||
src/gui/dbguielement.py
|
src/gui/dbguielement.py
|
||||||
src/gui/navigator.py
|
src/gui/navigator.py
|
||||||
src/gui/pluginmanager.py
|
src/gui/pluginmanager.py
|
||||||
src/gui/sidebar.py
|
|
||||||
|
|
||||||
# gui/editors - the GUI editors package
|
# gui/editors - the GUI editors package
|
||||||
src/gui/editors/__init__.py
|
src/gui/editors/__init__.py
|
||||||
|
@ -15,18 +15,17 @@ pkgdatadir = $(datadir)/@PACKAGE@/gui
|
|||||||
pkgdata_PYTHON = \
|
pkgdata_PYTHON = \
|
||||||
__init__.py \
|
__init__.py \
|
||||||
basesidebar.py \
|
basesidebar.py \
|
||||||
bottombar.py \
|
|
||||||
columnorder.py \
|
columnorder.py \
|
||||||
configure.py \
|
configure.py \
|
||||||
dbguielement.py \
|
dbguielement.py \
|
||||||
dbloader.py \
|
dbloader.py \
|
||||||
dbman.py \
|
dbman.py \
|
||||||
filtereditor.py \
|
filtereditor.py \
|
||||||
|
grampsbar.py \
|
||||||
grampsgui.py \
|
grampsgui.py \
|
||||||
navigator.py \
|
navigator.py \
|
||||||
makefilter.py \
|
makefilter.py \
|
||||||
pluginmanager.py \
|
pluginmanager.py \
|
||||||
sidebar.py \
|
|
||||||
utils.py \
|
utils.py \
|
||||||
viewmanager.py
|
viewmanager.py
|
||||||
|
|
||||||
|
@ -49,6 +49,7 @@ from gui.widgets.grampletpane import (AVAILABLE_GRAMPLETS,
|
|||||||
get_gramplet_options_by_name,
|
get_gramplet_options_by_name,
|
||||||
make_requested_gramplet)
|
make_requested_gramplet)
|
||||||
from ListModel import ListModel, NOSORT
|
from ListModel import ListModel, NOSORT
|
||||||
|
from QuestionDialog import ErrorDialog
|
||||||
|
|
||||||
#-------------------------------------------------------------------------
|
#-------------------------------------------------------------------------
|
||||||
#
|
#
|
||||||
@ -59,12 +60,12 @@ NL = "\n"
|
|||||||
|
|
||||||
#-------------------------------------------------------------------------
|
#-------------------------------------------------------------------------
|
||||||
#
|
#
|
||||||
# Bottombar class
|
# GrampsBar class
|
||||||
#
|
#
|
||||||
#-------------------------------------------------------------------------
|
#-------------------------------------------------------------------------
|
||||||
class Bottombar(object):
|
class GrampsBar(object):
|
||||||
"""
|
"""
|
||||||
A class which defines the graphical representation of the Gramps bottom bar.
|
A class which defines the graphical representation of the Gramps bar.
|
||||||
"""
|
"""
|
||||||
def __init__(self, dbstate, uistate, configfile, close_callback, defaults):
|
def __init__(self, dbstate, uistate, configfile, close_callback, defaults):
|
||||||
|
|
||||||
@ -73,61 +74,61 @@ class Bottombar(object):
|
|||||||
self.configfile = os.path.join(const.VERSION_DIR, "%s.ini" % configfile)
|
self.configfile = os.path.join(const.VERSION_DIR, "%s.ini" % configfile)
|
||||||
self.close_callback = close_callback
|
self.close_callback = close_callback
|
||||||
self.gramplet_map = {} # title->gramplet
|
self.gramplet_map = {} # title->gramplet
|
||||||
|
self.filter_page = 0
|
||||||
|
|
||||||
self.top = gtk.HBox()
|
|
||||||
self.notebook = gtk.Notebook()
|
self.notebook = gtk.Notebook()
|
||||||
self.notebook.set_show_border(False)
|
self.notebook.set_show_border(False)
|
||||||
self.notebook.set_scrollable(True)
|
self.notebook.set_scrollable(True)
|
||||||
self.notebook.connect('switch_page', self.__switch_page)
|
self.notebook.connect('switch_page', self.__switch_page)
|
||||||
|
|
||||||
vbox = gtk.VBox()
|
self.top = self._build_interface(self.notebook)
|
||||||
|
|
||||||
|
self.default_gramplets = defaults
|
||||||
|
config_settings, opts_list = self.load_gramplets()
|
||||||
|
|
||||||
|
opts_list.sort(key=lambda opt: opt["page"])
|
||||||
|
for opts in opts_list:
|
||||||
|
name = opts["name"]
|
||||||
|
all_opts = get_gramplet_opts(name, opts)
|
||||||
|
all_opts["layout"] = "tabs"
|
||||||
|
gramplet = make_requested_gramplet(self, name, all_opts,
|
||||||
|
self.dbstate, self.uistate)
|
||||||
|
self.gramplet_map[all_opts["title"]] = gramplet
|
||||||
|
self.__add_tab(gramplet)
|
||||||
|
|
||||||
|
if config_settings[0]:
|
||||||
|
self.top.show()
|
||||||
|
self.notebook.set_current_page(config_settings[1])
|
||||||
|
|
||||||
|
def _build_interface(self, notebook):
|
||||||
|
"""
|
||||||
|
Build the user interface. Must be implemented in adervied class.
|
||||||
|
"""
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def _make_buttons(self):
|
||||||
|
"""
|
||||||
|
Make the buttons.
|
||||||
|
"""
|
||||||
close_button = gtk.Button()
|
close_button = gtk.Button()
|
||||||
img = gtk.image_new_from_stock(gtk.STOCK_CLOSE, gtk.ICON_SIZE_MENU)
|
img = gtk.image_new_from_stock(gtk.STOCK_CLOSE, gtk.ICON_SIZE_MENU)
|
||||||
close_button.set_image(img)
|
close_button.set_image(img)
|
||||||
close_button.set_relief(gtk.RELIEF_NONE)
|
close_button.set_relief(gtk.RELIEF_NONE)
|
||||||
close_button.connect('clicked', self.__close_clicked)
|
close_button.connect('clicked', self.__close_clicked)
|
||||||
vbox.pack_start(close_button, False)
|
|
||||||
|
|
||||||
delete_button = gtk.Button()
|
delete_button = gtk.Button()
|
||||||
img = gtk.image_new_from_stock(gtk.STOCK_REMOVE, gtk.ICON_SIZE_MENU)
|
img = gtk.image_new_from_stock(gtk.STOCK_REMOVE, gtk.ICON_SIZE_MENU)
|
||||||
delete_button.set_image(img)
|
delete_button.set_image(img)
|
||||||
delete_button.set_relief(gtk.RELIEF_NONE)
|
delete_button.set_relief(gtk.RELIEF_NONE)
|
||||||
delete_button.connect('clicked', self.__delete_clicked)
|
delete_button.connect('clicked', self.__delete_clicked)
|
||||||
vbox.pack_end(delete_button, False)
|
|
||||||
|
|
||||||
add_button = gtk.Button()
|
add_button = gtk.Button()
|
||||||
img = gtk.image_new_from_stock(gtk.STOCK_ADD, gtk.ICON_SIZE_MENU)
|
img = gtk.image_new_from_stock(gtk.STOCK_ADD, gtk.ICON_SIZE_MENU)
|
||||||
add_button.set_image(img)
|
add_button.set_image(img)
|
||||||
add_button.set_relief(gtk.RELIEF_NONE)
|
add_button.set_relief(gtk.RELIEF_NONE)
|
||||||
add_button.connect('clicked', self.__add_clicked)
|
add_button.connect('clicked', self.__add_clicked)
|
||||||
vbox.pack_end(add_button, False)
|
|
||||||
|
|
||||||
self.top.pack_start(self.notebook, True)
|
return (close_button, delete_button, add_button)
|
||||||
self.top.pack_start(vbox, False)
|
|
||||||
|
|
||||||
vbox.show_all()
|
|
||||||
self.notebook.show()
|
|
||||||
|
|
||||||
self.default_gramplets = defaults
|
|
||||||
config_settings = self.load_gramplets()
|
|
||||||
|
|
||||||
for (name, opts) in config_settings[1]:
|
|
||||||
all_opts = get_gramplet_opts(name, opts)
|
|
||||||
all_opts["layout"] = "tabs"
|
|
||||||
gramplet = make_requested_gramplet(self, name, all_opts,
|
|
||||||
self.dbstate, self.uistate)
|
|
||||||
self.gramplet_map[all_opts["title"]] = gramplet
|
|
||||||
|
|
||||||
gramplets = [g for g in self.gramplet_map.itervalues()
|
|
||||||
if g is not None]
|
|
||||||
gramplets.sort(key=lambda x: x.page)
|
|
||||||
for gramplet in gramplets:
|
|
||||||
self.__add_tab(gramplet)
|
|
||||||
|
|
||||||
if config_settings[0][0]:
|
|
||||||
self.top.show()
|
|
||||||
self.notebook.set_current_page(config_settings[0][1])
|
|
||||||
|
|
||||||
def load_gramplets(self):
|
def load_gramplets(self):
|
||||||
"""
|
"""
|
||||||
@ -163,12 +164,12 @@ class Bottombar(object):
|
|||||||
if "name" not in data:
|
if "name" not in data:
|
||||||
data["name"] = "Unnamed Gramplet"
|
data["name"] = "Unnamed Gramplet"
|
||||||
data["tname"]= _("Unnamed Gramplet")
|
data["tname"]= _("Unnamed Gramplet")
|
||||||
retval.append((data["name"], data)) # name, opts
|
retval.append(data)
|
||||||
else:
|
else:
|
||||||
# give defaults as currently known
|
# give defaults as currently known
|
||||||
for name in self.default_gramplets:
|
for name in self.default_gramplets:
|
||||||
if name in AVAILABLE_GRAMPLETS():
|
if name in AVAILABLE_GRAMPLETS():
|
||||||
retval.append((name, GET_AVAILABLE_GRAMPLETS(name)))
|
retval.append(GET_AVAILABLE_GRAMPLETS(name))
|
||||||
return ((visible, default_page), retval)
|
return ((visible, default_page), retval)
|
||||||
|
|
||||||
def save(self):
|
def save(self):
|
||||||
@ -194,7 +195,7 @@ class Bottombar(object):
|
|||||||
for page_num in range(self.notebook.get_n_pages()):
|
for page_num in range(self.notebook.get_n_pages()):
|
||||||
title = get_title(self.notebook, page_num)
|
title = get_title(self.notebook, page_num)
|
||||||
gramplet = self.gramplet_map[title]
|
gramplet = self.gramplet_map[title]
|
||||||
|
if gramplet is None: continue # filter tab
|
||||||
opts = get_gramplet_options_by_name(gramplet.name)
|
opts = get_gramplet_options_by_name(gramplet.name)
|
||||||
if opts is not None:
|
if opts is not None:
|
||||||
base_opts = opts.copy()
|
base_opts = opts.copy()
|
||||||
@ -231,7 +232,8 @@ class Bottombar(object):
|
|||||||
"""
|
"""
|
||||||
page = self.notebook.get_current_page()
|
page = self.notebook.get_current_page()
|
||||||
title = get_title(self.notebook, page)
|
title = get_title(self.notebook, page)
|
||||||
if title is not None and self.gramplet_map[title].pui:
|
if title is not None and self.gramplet_map[title] \
|
||||||
|
and self.gramplet_map[title].pui:
|
||||||
self.gramplet_map[title].pui.active = True
|
self.gramplet_map[title].pui.active = True
|
||||||
if self.gramplet_map[title].pui.dirty:
|
if self.gramplet_map[title].pui.dirty:
|
||||||
self.gramplet_map[title].pui.update()
|
self.gramplet_map[title].pui.update()
|
||||||
@ -242,7 +244,8 @@ class Bottombar(object):
|
|||||||
"""
|
"""
|
||||||
page = self.notebook.get_current_page()
|
page = self.notebook.get_current_page()
|
||||||
title = get_title(self.notebook, page)
|
title = get_title(self.notebook, page)
|
||||||
if title is not None and self.gramplet_map[title].pui:
|
if title is not None and self.gramplet_map[title] \
|
||||||
|
and self.gramplet_map[title].pui:
|
||||||
if self.gramplet_map[title].state != "detached":
|
if self.gramplet_map[title].state != "detached":
|
||||||
self.gramplet_map[title].pui.active = False
|
self.gramplet_map[title].pui.active = False
|
||||||
|
|
||||||
@ -276,6 +279,32 @@ class Bottombar(object):
|
|||||||
"""
|
"""
|
||||||
return self.top.hide()
|
return self.top.hide()
|
||||||
|
|
||||||
|
def is_visible(self):
|
||||||
|
"""
|
||||||
|
Return True if the bar is visible, else return False.
|
||||||
|
"""
|
||||||
|
return self.top.get_property('visible')
|
||||||
|
|
||||||
|
def add_filter(self, filter):
|
||||||
|
"""
|
||||||
|
Add a filter.
|
||||||
|
"""
|
||||||
|
self.gramplet_map[_('Filter')] = None
|
||||||
|
self.notebook.prepend_page(filter, gtk.Label(_('Filter')))
|
||||||
|
self.notebook.set_tab_reorderable(filter, True)
|
||||||
|
self.notebook.set_current_page(0)
|
||||||
|
|
||||||
|
def remove_filter(self):
|
||||||
|
"""
|
||||||
|
Remove the filter.
|
||||||
|
"""
|
||||||
|
for page_num in range(self.notebook.get_n_pages()):
|
||||||
|
title = get_title(self.notebook, page_num)
|
||||||
|
if title == _('Filter'):
|
||||||
|
self.notebook.remove_page(page_num)
|
||||||
|
del self.gramplet_map[_('Filter')]
|
||||||
|
return
|
||||||
|
|
||||||
def __close_clicked(self, button):
|
def __close_clicked(self, button):
|
||||||
"""
|
"""
|
||||||
Called when the sidebar is closed.
|
Called when the sidebar is closed.
|
||||||
@ -288,7 +317,8 @@ class Bottombar(object):
|
|||||||
"""
|
"""
|
||||||
names = [GET_AVAILABLE_GRAMPLETS(key)["tname"] for key
|
names = [GET_AVAILABLE_GRAMPLETS(key)["tname"] for key
|
||||||
in AVAILABLE_GRAMPLETS()]
|
in AVAILABLE_GRAMPLETS()]
|
||||||
skip = [gramplet.tname for gramplet in self.gramplet_map.values()]
|
skip = [gramplet.tname for gramplet in self.gramplet_map.values()
|
||||||
|
if gramplet is not None]
|
||||||
gramplet_list = [name for name in names if name not in skip]
|
gramplet_list = [name for name in names if name not in skip]
|
||||||
gramplet_list.sort()
|
gramplet_list.sort()
|
||||||
dialog = ChooseGrampletDialog(_("Select Gramplet"), gramplet_list)
|
dialog = ChooseGrampletDialog(_("Select Gramplet"), gramplet_list)
|
||||||
@ -325,8 +355,13 @@ class Bottombar(object):
|
|||||||
"""
|
"""
|
||||||
page_num = self.notebook.get_current_page()
|
page_num = self.notebook.get_current_page()
|
||||||
title = get_title(self.notebook, page_num)
|
title = get_title(self.notebook, page_num)
|
||||||
del self.gramplet_map[title]
|
if self.gramplet_map[title] is None:
|
||||||
self.notebook.remove_page(page_num)
|
ErrorDialog(
|
||||||
|
_("Cannot remove tab"),
|
||||||
|
_("The filter tab cannot be removed"))
|
||||||
|
else:
|
||||||
|
del self.gramplet_map[title]
|
||||||
|
self.notebook.remove_page(page_num)
|
||||||
|
|
||||||
def __switch_page(self, notebook, unused, new_page):
|
def __switch_page(self, notebook, unused, new_page):
|
||||||
"""
|
"""
|
||||||
@ -336,12 +371,12 @@ class Bottombar(object):
|
|||||||
#print "switch from", old_page, "to", new_page
|
#print "switch from", old_page, "to", new_page
|
||||||
if old_page >= 0:
|
if old_page >= 0:
|
||||||
title = get_title(notebook, old_page)
|
title = get_title(notebook, old_page)
|
||||||
if self.gramplet_map[title].pui:
|
if self.gramplet_map[title] and self.gramplet_map[title].pui:
|
||||||
if self.gramplet_map[title].state != "detached":
|
if self.gramplet_map[title].state != "detached":
|
||||||
self.gramplet_map[title].pui.active = False
|
self.gramplet_map[title].pui.active = False
|
||||||
|
|
||||||
title = get_title(notebook, new_page)
|
title = get_title(notebook, new_page)
|
||||||
if self.gramplet_map[title].pui:
|
if self.gramplet_map[title] and self.gramplet_map[title].pui:
|
||||||
self.gramplet_map[title].pui.active = True
|
self.gramplet_map[title].pui.active = True
|
||||||
if self.gramplet_map[title].pui.dirty:
|
if self.gramplet_map[title].pui.dirty:
|
||||||
self.gramplet_map[title].pui.update()
|
self.gramplet_map[title].pui.update()
|
||||||
@ -355,6 +390,66 @@ def get_title(notebook, page_num):
|
|||||||
return None
|
return None
|
||||||
else:
|
else:
|
||||||
return notebook.get_tab_label_text(page)
|
return notebook.get_tab_label_text(page)
|
||||||
|
|
||||||
|
#-------------------------------------------------------------------------
|
||||||
|
#
|
||||||
|
# HBar class
|
||||||
|
#
|
||||||
|
#-------------------------------------------------------------------------
|
||||||
|
class HBar(GrampsBar):
|
||||||
|
"""
|
||||||
|
A class which defines the representation of a horizontal Gramps bar.
|
||||||
|
"""
|
||||||
|
def _build_interface(self, notebook):
|
||||||
|
"""
|
||||||
|
Build the horizontal user interface.
|
||||||
|
"""
|
||||||
|
top = gtk.HBox()
|
||||||
|
vbox = gtk.VBox()
|
||||||
|
|
||||||
|
close_button, delete_button, add_button = self._make_buttons()
|
||||||
|
|
||||||
|
vbox.pack_start(close_button, False)
|
||||||
|
vbox.pack_end(delete_button, False)
|
||||||
|
vbox.pack_end(add_button, False)
|
||||||
|
|
||||||
|
top.pack_start(notebook, True)
|
||||||
|
top.pack_start(vbox, False)
|
||||||
|
|
||||||
|
notebook.show()
|
||||||
|
vbox.show_all()
|
||||||
|
|
||||||
|
return top
|
||||||
|
|
||||||
|
#-------------------------------------------------------------------------
|
||||||
|
#
|
||||||
|
# VBar class
|
||||||
|
#
|
||||||
|
#-------------------------------------------------------------------------
|
||||||
|
class VBar(GrampsBar):
|
||||||
|
"""
|
||||||
|
A class which defines the representation of a vertical Gramps bar.
|
||||||
|
"""
|
||||||
|
def _build_interface(self, notebook):
|
||||||
|
"""
|
||||||
|
Build the vertical user interface.
|
||||||
|
"""
|
||||||
|
top = gtk.VBox()
|
||||||
|
hbox = gtk.HBox()
|
||||||
|
|
||||||
|
close_button, delete_button, add_button = self._make_buttons()
|
||||||
|
|
||||||
|
hbox.pack_start(add_button, False)
|
||||||
|
hbox.pack_start(delete_button, False)
|
||||||
|
hbox.pack_end(close_button, False)
|
||||||
|
|
||||||
|
top.pack_start(hbox, False)
|
||||||
|
top.pack_start(notebook, True)
|
||||||
|
|
||||||
|
notebook.show()
|
||||||
|
hbox.show_all()
|
||||||
|
|
||||||
|
return top
|
||||||
|
|
||||||
#-------------------------------------------------------------------------
|
#-------------------------------------------------------------------------
|
||||||
#
|
#
|
@ -1,197 +0,0 @@
|
|||||||
#
|
|
||||||
# Gramps - a GTK+/GNOME based genealogy program
|
|
||||||
#
|
|
||||||
# Copyright (C) 2010 Nick Hall
|
|
||||||
#
|
|
||||||
# This program 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 2 of the License, or
|
|
||||||
# (at your option) any later version.
|
|
||||||
#
|
|
||||||
# This program 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, write to the Free Software
|
|
||||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
||||||
#
|
|
||||||
# $Id$
|
|
||||||
|
|
||||||
#-------------------------------------------------------------------------
|
|
||||||
#
|
|
||||||
# Python modules
|
|
||||||
#
|
|
||||||
#-------------------------------------------------------------------------
|
|
||||||
from gen.ggettext import gettext as _
|
|
||||||
|
|
||||||
#-------------------------------------------------------------------------
|
|
||||||
#
|
|
||||||
# GNOME modules
|
|
||||||
#
|
|
||||||
#-------------------------------------------------------------------------
|
|
||||||
import gtk
|
|
||||||
|
|
||||||
#-------------------------------------------------------------------------
|
|
||||||
#
|
|
||||||
# Sidebar class
|
|
||||||
#
|
|
||||||
#-------------------------------------------------------------------------
|
|
||||||
class Sidebar(object):
|
|
||||||
"""
|
|
||||||
A class which defines the graphical representation of the Gramps sidebar.
|
|
||||||
"""
|
|
||||||
def __init__(self, changed_callback, close_callback):
|
|
||||||
|
|
||||||
self.changed_callback = changed_callback
|
|
||||||
self.close_callback = close_callback
|
|
||||||
self.pages = []
|
|
||||||
self.top = gtk.VBox()
|
|
||||||
|
|
||||||
frame = gtk.Frame()
|
|
||||||
hbox = gtk.HBox()
|
|
||||||
frame.add(hbox)
|
|
||||||
|
|
||||||
select_button = gtk.ToggleButton()
|
|
||||||
select_button.set_relief(gtk.RELIEF_NONE)
|
|
||||||
select_hbox = gtk.HBox()
|
|
||||||
self.title_label = gtk.Label()
|
|
||||||
arrow = gtk.Arrow(gtk.ARROW_DOWN, gtk.SHADOW_NONE)
|
|
||||||
select_hbox.pack_start(self.title_label, False)
|
|
||||||
select_hbox.pack_end(arrow, False)
|
|
||||||
select_button.add(select_hbox)
|
|
||||||
|
|
||||||
select_button.connect('button_press_event', self.__menu_button_pressed)
|
|
||||||
|
|
||||||
close_button = gtk.Button()
|
|
||||||
img = gtk.image_new_from_stock(gtk.STOCK_CLOSE, gtk.ICON_SIZE_MENU)
|
|
||||||
close_button.set_image(img)
|
|
||||||
close_button.set_relief(gtk.RELIEF_NONE)
|
|
||||||
close_button.connect('clicked', self.__close_clicked)
|
|
||||||
hbox.pack_start(select_button, False)
|
|
||||||
hbox.pack_end(close_button, False)
|
|
||||||
#frame.show_all()
|
|
||||||
|
|
||||||
self.top.pack_start(frame, False)
|
|
||||||
|
|
||||||
self.menu = gtk.Menu()
|
|
||||||
self.menu.show()
|
|
||||||
self.menu.connect('deactivate', self.__menu_deactivate, select_button)
|
|
||||||
|
|
||||||
self.notebook = gtk.Notebook()
|
|
||||||
self.notebook.show()
|
|
||||||
self.notebook.set_show_tabs(True)
|
|
||||||
self.notebook.set_show_border(False)
|
|
||||||
self.notebook.connect('switch_page', self.__switch_page)
|
|
||||||
self.top.pack_start(self.notebook, True)
|
|
||||||
self.top.show()
|
|
||||||
|
|
||||||
def get_display(self):
|
|
||||||
"""
|
|
||||||
Return the top container widget for the GUI.
|
|
||||||
"""
|
|
||||||
return self.top
|
|
||||||
|
|
||||||
def show(self):
|
|
||||||
"""
|
|
||||||
Display the sidebar.
|
|
||||||
"""
|
|
||||||
self.top.show()
|
|
||||||
|
|
||||||
def hide(self):
|
|
||||||
"""
|
|
||||||
Hide the sidebar.
|
|
||||||
"""
|
|
||||||
self.top.hide()
|
|
||||||
|
|
||||||
def set_current_page(self, page_num):
|
|
||||||
"""
|
|
||||||
Set the sidebar page.
|
|
||||||
"""
|
|
||||||
self.notebook.set_current_page(page_num)
|
|
||||||
|
|
||||||
def get_page_type(self):
|
|
||||||
"""
|
|
||||||
Return the type of the active page.
|
|
||||||
"""
|
|
||||||
return self.pages[self.notebook.get_current_page()][1]
|
|
||||||
|
|
||||||
def add(self, title, container, page_type):
|
|
||||||
"""
|
|
||||||
Add a page to the sidebar.
|
|
||||||
"""
|
|
||||||
menu_item = gtk.MenuItem(title)
|
|
||||||
self.pages.append([title, page_type, menu_item])
|
|
||||||
index = self.notebook.append_page(container, gtk.Label(title))
|
|
||||||
menu_item.connect('activate', self.__menu_activate, index)
|
|
||||||
menu_item.show()
|
|
||||||
self.menu.append(menu_item)
|
|
||||||
self.notebook.set_current_page(index)
|
|
||||||
|
|
||||||
def remove(self, page_type):
|
|
||||||
"""
|
|
||||||
Replace a page in the sidebar.
|
|
||||||
"""
|
|
||||||
position = self.__get_page(page_type)
|
|
||||||
if position is not None:
|
|
||||||
self.notebook.remove_page(position)
|
|
||||||
self.menu.remove(self.pages[position][2])
|
|
||||||
self.pages = self.pages[:position] + self.pages[position+1:]
|
|
||||||
|
|
||||||
def __get_page(self, page_type):
|
|
||||||
"""
|
|
||||||
Return the page number of the page with the given type.
|
|
||||||
"""
|
|
||||||
for page_num, page in enumerate(self.pages):
|
|
||||||
if page[1] == page_type:
|
|
||||||
return page_num
|
|
||||||
return None
|
|
||||||
|
|
||||||
def __menu_button_pressed(self, button, event):
|
|
||||||
"""
|
|
||||||
Called when the button to select a sidebar page is pressed.
|
|
||||||
"""
|
|
||||||
if event.button == 1 and event.type == gtk.gdk.BUTTON_PRESS:
|
|
||||||
button.grab_focus()
|
|
||||||
button.set_active(True)
|
|
||||||
|
|
||||||
self.menu.popup(None, None, self.__menu_position, event.button,
|
|
||||||
event.time, button)
|
|
||||||
|
|
||||||
def __menu_position(self, menu, button):
|
|
||||||
"""
|
|
||||||
Determine the position of the popup menu.
|
|
||||||
"""
|
|
||||||
x, y = button.window.get_origin()
|
|
||||||
x += button.allocation.x
|
|
||||||
y += button.allocation.y + button.allocation.height
|
|
||||||
|
|
||||||
return (x, y, False)
|
|
||||||
|
|
||||||
def __menu_activate(self, menu, index):
|
|
||||||
"""
|
|
||||||
Called when an item in the popup menu is selected.
|
|
||||||
"""
|
|
||||||
self.notebook.set_current_page(index)
|
|
||||||
|
|
||||||
def __menu_deactivate(self, menu, button):
|
|
||||||
"""
|
|
||||||
Called when the popup menu disappears.
|
|
||||||
"""
|
|
||||||
button.set_active(False)
|
|
||||||
|
|
||||||
def __switch_page(self, notebook, unused, index):
|
|
||||||
"""
|
|
||||||
Called when the user has switched to a new sidebar page.
|
|
||||||
"""
|
|
||||||
if self.pages:
|
|
||||||
self.title_label.set_markup('<b>%s</b>' % self.pages[index][0])
|
|
||||||
active = self.top.get_property('visible')
|
|
||||||
self.changed_callback(self.pages[index][1], active, index)
|
|
||||||
|
|
||||||
def __close_clicked(self, button):
|
|
||||||
"""
|
|
||||||
Called when the sidebar is closed.
|
|
||||||
"""
|
|
||||||
self.close_callback()
|
|
@ -1156,7 +1156,6 @@ class ViewManager(CLIManager):
|
|||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
return
|
return
|
||||||
|
|
||||||
page.define_actions()
|
|
||||||
try:
|
try:
|
||||||
page_display = page.get_display()
|
page_display = page.get_display()
|
||||||
except:
|
except:
|
||||||
@ -1164,11 +1163,12 @@ class ViewManager(CLIManager):
|
|||||||
print("ERROR: '%s' failed to create view" % pdata.name)
|
print("ERROR: '%s' failed to create view" % pdata.name)
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
return
|
return
|
||||||
|
page.define_actions()
|
||||||
page.post()
|
page.post()
|
||||||
|
|
||||||
self.pages.append(page)
|
self.pages.append(page)
|
||||||
|
|
||||||
# create icon/label for workspace notebook
|
# create icon/label for notebook tab (useful for debugging)
|
||||||
hbox = gtk.HBox()
|
hbox = gtk.HBox()
|
||||||
image = gtk.Image()
|
image = gtk.Image()
|
||||||
image.set_from_stock(page.get_stock(), gtk.ICON_SIZE_MENU)
|
image.set_from_stock(page.get_stock(), gtk.ICON_SIZE_MENU)
|
||||||
|
@ -50,7 +50,7 @@ import pango
|
|||||||
# GRAMPS
|
# GRAMPS
|
||||||
#
|
#
|
||||||
#----------------------------------------------------------------
|
#----------------------------------------------------------------
|
||||||
from gui.views.pageview import PageView, FILTER_PAGE
|
from gui.views.pageview import PageView
|
||||||
from gui.views.navigationview import NavigationView
|
from gui.views.navigationview import NavigationView
|
||||||
from gui.columnorder import ColumnOrder
|
from gui.columnorder import ColumnOrder
|
||||||
import config
|
import config
|
||||||
@ -322,6 +322,15 @@ class ListView(NavigationView):
|
|||||||
[(self.colinfo[pair[1]], pair[1], pair[1] in self.exact_search())
|
[(self.colinfo[pair[1]], pair[1], pair[1] in self.exact_search())
|
||||||
for pair in self.column_order() if pair[0]])
|
for pair in self.column_order() if pair[0]])
|
||||||
|
|
||||||
|
def sidebar_toggled(self, active):
|
||||||
|
"""
|
||||||
|
Called when the sidebar is toggled.
|
||||||
|
"""
|
||||||
|
if active:
|
||||||
|
self.search_bar.hide()
|
||||||
|
else:
|
||||||
|
self.search_bar.show()
|
||||||
|
|
||||||
####################################################################
|
####################################################################
|
||||||
# Navigation
|
# Navigation
|
||||||
####################################################################
|
####################################################################
|
||||||
@ -862,16 +871,6 @@ class ListView(NavigationView):
|
|||||||
self.edit_action.set_visible(True)
|
self.edit_action.set_visible(True)
|
||||||
self.edit_action.set_sensitive(not self.dbstate.db.readonly)
|
self.edit_action.set_sensitive(not self.dbstate.db.readonly)
|
||||||
|
|
||||||
def sidebar_changed(self, page_type, active, index):
|
|
||||||
"""
|
|
||||||
Called when the sidebar page is changed.
|
|
||||||
"""
|
|
||||||
PageView.sidebar_changed(self, page_type, active, index)
|
|
||||||
if active and page_type == FILTER_PAGE:
|
|
||||||
self.search_bar.hide()
|
|
||||||
else:
|
|
||||||
self.search_bar.show()
|
|
||||||
|
|
||||||
def on_delete(self):
|
def on_delete(self):
|
||||||
"""
|
"""
|
||||||
Save the column widths when the view is shutdown.
|
Save the column widths when the view is shutdown.
|
||||||
|
@ -49,20 +49,10 @@ from gen.ggettext import gettext as _
|
|||||||
import Errors
|
import Errors
|
||||||
from gui.dbguielement import DbGUIElement
|
from gui.dbguielement import DbGUIElement
|
||||||
from gui.widgets.menutoolbuttonaction import MenuToolButtonAction
|
from gui.widgets.menutoolbuttonaction import MenuToolButtonAction
|
||||||
from gui.sidebar import Sidebar
|
from gui.grampsbar import HBar, VBar
|
||||||
from gui.bottombar import Bottombar
|
|
||||||
from gui.widgets.grampletpane import GrampletPane
|
|
||||||
from gui.configure import ConfigureDialog
|
from gui.configure import ConfigureDialog
|
||||||
from config import config
|
from config import config
|
||||||
|
|
||||||
#-------------------------------------------------------------------------
|
|
||||||
#
|
|
||||||
# Constants
|
|
||||||
#
|
|
||||||
#-------------------------------------------------------------------------
|
|
||||||
GRAMPLET_PAGE = 0
|
|
||||||
FILTER_PAGE = 1
|
|
||||||
|
|
||||||
#------------------------------------------------------------------------------
|
#------------------------------------------------------------------------------
|
||||||
#
|
#
|
||||||
# PageView
|
# PageView
|
||||||
@ -121,10 +111,6 @@ class PageView(DbGUIElement):
|
|||||||
</placeholder>
|
</placeholder>
|
||||||
</menu>
|
</menu>
|
||||||
</menubar>
|
</menubar>
|
||||||
<popup name="GrampletPopup">
|
|
||||||
<menuitem action="AddGramplet"/>
|
|
||||||
<menuitem action="RestoreGramplet"/>
|
|
||||||
</popup>
|
|
||||||
</ui>'''
|
</ui>'''
|
||||||
self.dirty = True
|
self.dirty = True
|
||||||
self.active = False
|
self.active = False
|
||||||
@ -150,7 +136,6 @@ class PageView(DbGUIElement):
|
|||||||
|
|
||||||
self.filter_class = None
|
self.filter_class = None
|
||||||
self.top = None
|
self.top = None
|
||||||
self.gramplet_pane = None
|
|
||||||
self.sidebar = None
|
self.sidebar = None
|
||||||
self.bottombar = None
|
self.bottombar = None
|
||||||
|
|
||||||
@ -161,12 +146,15 @@ class PageView(DbGUIElement):
|
|||||||
Builds the container widget for the interface.
|
Builds the container widget for the interface.
|
||||||
Returns a gtk container widget.
|
Returns a gtk container widget.
|
||||||
"""
|
"""
|
||||||
self.sidebar = Sidebar(self.sidebar_changed, self.sidebar_closed)
|
defaults = self.get_default_gramplets()
|
||||||
defaults = self.get_default_gramplets()[1]
|
self.sidebar = VBar(self.dbstate, self.uistate,
|
||||||
self.bottombar = Bottombar(self.dbstate, self.uistate,
|
self.ident + "_sidebar",
|
||||||
|
self.sidebar_closed,
|
||||||
|
defaults[0])
|
||||||
|
self.bottombar = HBar(self.dbstate, self.uistate,
|
||||||
self.ident + "_bottombar",
|
self.ident + "_bottombar",
|
||||||
self.bottombar_closed,
|
self.bottombar_closed,
|
||||||
defaults)
|
defaults[1])
|
||||||
hpane = gtk.HPaned()
|
hpane = gtk.HPaned()
|
||||||
vpane = gtk.VPaned()
|
vpane = gtk.VPaned()
|
||||||
hpane.pack1(vpane, resize=True, shrink=False)
|
hpane.pack1(vpane, resize=True, shrink=False)
|
||||||
@ -178,56 +166,35 @@ class PageView(DbGUIElement):
|
|||||||
widget.show_all()
|
widget.show_all()
|
||||||
vpane.pack1(widget, resize=True, shrink=False)
|
vpane.pack1(widget, resize=True, shrink=False)
|
||||||
vpane.pack2(self.bottombar.get_display(), resize=False, shrink=False)
|
vpane.pack2(self.bottombar.get_display(), resize=False, shrink=False)
|
||||||
initial_page = self._config.get('sidebar.page')
|
|
||||||
|
|
||||||
self.gramplet_pane = self.__create_gramplet_pane()
|
|
||||||
|
|
||||||
if self.filter_class:
|
if self.filter_class:
|
||||||
self.add_filter(self.filter_class)
|
self.add_filter(self.filter_class)
|
||||||
|
self.sidebar_toggled(self.sidebar.is_visible())
|
||||||
self.sidebar.set_current_page(initial_page)
|
|
||||||
if self._config.get('sidebar.visible'):
|
|
||||||
self.sidebar.show()
|
|
||||||
else:
|
|
||||||
self.sidebar.hide()
|
|
||||||
|
|
||||||
return hpane
|
return hpane
|
||||||
|
|
||||||
def add_filter(self, filter_class):
|
def add_filter(self, filter_class):
|
||||||
"""
|
"""
|
||||||
Add a filter to the workspace sidebar.
|
Add a filter to the sidebar.
|
||||||
"""
|
"""
|
||||||
self.filter_sidebar = filter_class(self.dbstate, self.uistate,
|
self.filter_tab = filter_class(self.dbstate, self.uistate,
|
||||||
self.__filter_clicked)
|
self.__filter_clicked)
|
||||||
top = self.filter_sidebar.get_widget()
|
top = self.filter_tab.get_widget()
|
||||||
top.show_all()
|
top.show_all()
|
||||||
self.sidebar.add(_('Filter'), top, FILTER_PAGE)
|
self.sidebar.add_filter(top)
|
||||||
|
|
||||||
def remove_filter(self):
|
def remove_filter(self):
|
||||||
"""
|
"""
|
||||||
Remove the filter from the workspace sidebar.
|
Remove the filter from the sidebar.
|
||||||
"""
|
"""
|
||||||
self.filter_sidebar = None
|
self.filter_tab = None
|
||||||
self.sidebar.remove(FILTER_PAGE)
|
self.sidebar.remove_filter()
|
||||||
|
|
||||||
def __create_gramplet_pane(self):
|
|
||||||
"""
|
|
||||||
Create a gramplet pane.
|
|
||||||
"""
|
|
||||||
defaults = self.get_default_gramplets()[0]
|
|
||||||
gramplet_pane = GrampletPane(self.ident + "_sidebar",
|
|
||||||
self, self.dbstate, self.uistate,
|
|
||||||
column_count=1,
|
|
||||||
default_gramplets=defaults)
|
|
||||||
gramplet_pane.show_all()
|
|
||||||
self.sidebar.add(_('Gramplets'), gramplet_pane, GRAMPLET_PAGE)
|
|
||||||
return gramplet_pane
|
|
||||||
|
|
||||||
def __filter_clicked(self):
|
def __filter_clicked(self):
|
||||||
"""
|
"""
|
||||||
Called when the filter 'Find' button is clicked.
|
Called when the filter 'Find' button is clicked.
|
||||||
"""
|
"""
|
||||||
self.generic_filter = self.filter_sidebar.get_filter()
|
self.generic_filter = self.filter_tab.get_filter()
|
||||||
self.build_tree()
|
self.build_tree()
|
||||||
|
|
||||||
def __sidebar_toggled(self, action):
|
def __sidebar_toggled(self, action):
|
||||||
@ -237,11 +204,10 @@ class PageView(DbGUIElement):
|
|||||||
active = action.get_active()
|
active = action.get_active()
|
||||||
if active:
|
if active:
|
||||||
self.sidebar.show()
|
self.sidebar.show()
|
||||||
self.sidebar_changed(self.sidebar.get_page_type(), True, None)
|
self.sidebar_toggled(True)
|
||||||
else:
|
else:
|
||||||
self.sidebar.hide()
|
self.sidebar.hide()
|
||||||
self.sidebar_changed(None, False, None)
|
self.sidebar_toggled(False)
|
||||||
self._config.set('sidebar.visible', active)
|
|
||||||
|
|
||||||
def __bottombar_toggled(self, action):
|
def __bottombar_toggled(self, action):
|
||||||
"""
|
"""
|
||||||
@ -252,14 +218,12 @@ class PageView(DbGUIElement):
|
|||||||
self.bottombar.show()
|
self.bottombar.show()
|
||||||
else:
|
else:
|
||||||
self.bottombar.hide()
|
self.bottombar.hide()
|
||||||
self._config.set('bottombar.visible', active)
|
|
||||||
|
|
||||||
def sidebar_changed(self, page_type, active, index):
|
def sidebar_toggled(self, active):
|
||||||
"""
|
"""
|
||||||
Called when the sidebar page is changed.
|
Called when the sidebar is toggled.
|
||||||
"""
|
"""
|
||||||
if index is not None:
|
pass
|
||||||
self._config.set('sidebar.page', index)
|
|
||||||
|
|
||||||
def sidebar_closed(self):
|
def sidebar_closed(self):
|
||||||
"""
|
"""
|
||||||
@ -375,7 +339,7 @@ class PageView(DbGUIElement):
|
|||||||
Called with the PageView is set as active. If the page is "dirty",
|
Called with the PageView is set as active. If the page is "dirty",
|
||||||
then we rebuild the data.
|
then we rebuild the data.
|
||||||
"""
|
"""
|
||||||
self.gramplet_pane.set_active()
|
self.sidebar.set_active()
|
||||||
self.bottombar.set_active()
|
self.bottombar.set_active()
|
||||||
self.active = True
|
self.active = True
|
||||||
if self.dirty:
|
if self.dirty:
|
||||||
@ -387,7 +351,7 @@ class PageView(DbGUIElement):
|
|||||||
"""
|
"""
|
||||||
Marks page as being inactive (not currently displayed)
|
Marks page as being inactive (not currently displayed)
|
||||||
"""
|
"""
|
||||||
self.gramplet_pane.set_inactive()
|
self.sidebar.set_inactive()
|
||||||
self.bottombar.set_inactive()
|
self.bottombar.set_inactive()
|
||||||
self.active = False
|
self.active = False
|
||||||
|
|
||||||
@ -482,13 +446,9 @@ class PageView(DbGUIElement):
|
|||||||
self.action_toggle_list in this function.
|
self.action_toggle_list in this function.
|
||||||
"""
|
"""
|
||||||
self._add_toggle_action('Sidebar', None, _('_Sidebar'),
|
self._add_toggle_action('Sidebar', None, _('_Sidebar'),
|
||||||
None, None, self.__sidebar_toggled,
|
None, None, self.__sidebar_toggled, self.sidebar.is_visible())
|
||||||
self._config.get('sidebar.visible'))
|
|
||||||
self._add_toggle_action('Bottombar', None, _('_Bottombar'),
|
self._add_toggle_action('Bottombar', None, _('_Bottombar'),
|
||||||
None, None, self.__bottombar_toggled,
|
None, None, self.__bottombar_toggled, self.bottombar.is_visible())
|
||||||
self._config.get('bottombar.visible'))
|
|
||||||
self._add_action("AddGramplet", gtk.STOCK_ADD, _("Add a gramplet"))
|
|
||||||
self._add_action("RestoreGramplet", None, _("Restore a gramplet"))
|
|
||||||
|
|
||||||
def __build_action_group(self):
|
def __build_action_group(self):
|
||||||
"""
|
"""
|
||||||
@ -605,7 +565,7 @@ class PageView(DbGUIElement):
|
|||||||
Method called on shutdown. Data views should put code here
|
Method called on shutdown. Data views should put code here
|
||||||
that should be called when quiting the main application.
|
that should be called when quiting the main application.
|
||||||
"""
|
"""
|
||||||
self.gramplet_pane.on_delete()
|
self.sidebar.on_delete()
|
||||||
self.bottombar.on_delete()
|
self.bottombar.on_delete()
|
||||||
self._config.save()
|
self._config.save()
|
||||||
|
|
||||||
@ -629,10 +589,6 @@ class PageView(DbGUIElement):
|
|||||||
use_config_path=True)
|
use_config_path=True)
|
||||||
for section, value in self.CONFIGSETTINGS:
|
for section, value in self.CONFIGSETTINGS:
|
||||||
self._config.register(section, value)
|
self._config.register(section, value)
|
||||||
self._config.register('sidebar.visible', False)
|
|
||||||
self._config.register('sidebar.page', 0)
|
|
||||||
self._config.register('bottombar.visible', False)
|
|
||||||
self._config.register('bottombar.page', 0)
|
|
||||||
self._config.init()
|
self._config.init()
|
||||||
self.config_connect()
|
self.config_connect()
|
||||||
|
|
||||||
@ -668,37 +624,16 @@ class PageView(DbGUIElement):
|
|||||||
"""
|
"""
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
def __get_configure_funcs(self):
|
|
||||||
"""
|
|
||||||
Return a combined list of configuration functions for all of the panes
|
|
||||||
in the view.
|
|
||||||
|
|
||||||
:return: list of functions
|
|
||||||
"""
|
|
||||||
retval = []
|
|
||||||
if self.can_configure():
|
|
||||||
other = self._get_configure_page_funcs()
|
|
||||||
if callable(other):
|
|
||||||
retval += other()
|
|
||||||
else:
|
|
||||||
retval += other
|
|
||||||
|
|
||||||
if self.gramplet_pane is not None:
|
|
||||||
func = self.gramplet_pane._get_configure_page_funcs()
|
|
||||||
retval += func()
|
|
||||||
|
|
||||||
return retval
|
|
||||||
|
|
||||||
def configure(self):
|
def configure(self):
|
||||||
"""
|
"""
|
||||||
Open the configure dialog for the workspace.
|
Open the configure dialog for the view.
|
||||||
"""
|
"""
|
||||||
title = _("Configure %(cat)s - %(view)s") % \
|
title = _("Configure %(cat)s - %(view)s") % \
|
||||||
{'cat': self.get_translated_category(),
|
{'cat': self.get_translated_category(),
|
||||||
'view': self.get_title()}
|
'view': self.get_title()}
|
||||||
try:
|
try:
|
||||||
ViewConfigureDialog(self.uistate, self.dbstate,
|
ViewConfigureDialog(self.uistate, self.dbstate,
|
||||||
self.__get_configure_funcs(),
|
self._get_configure_page_funcs(),
|
||||||
self, self._config, dialogtitle=title,
|
self, self._config, dialogtitle=title,
|
||||||
ident=_("%(cat)s - %(view)s") %
|
ident=_("%(cat)s - %(view)s") %
|
||||||
{'cat': self.get_translated_category(),
|
{'cat': self.get_translated_category(),
|
||||||
|
@ -89,6 +89,7 @@ def GET_AVAILABLE_GRAMPLETS(name):
|
|||||||
"gramps": "0.0.0",
|
"gramps": "0.0.0",
|
||||||
"column": -1,
|
"column": -1,
|
||||||
"row": -1,
|
"row": -1,
|
||||||
|
"page": 0,
|
||||||
"data": [],
|
"data": [],
|
||||||
"help_url": gplug.help_url,
|
"help_url": gplug.help_url,
|
||||||
}
|
}
|
||||||
|
@ -88,6 +88,13 @@ class GrampletView(PageView):
|
|||||||
"""
|
"""
|
||||||
return 'gramps-gramplet'
|
return 'gramps-gramplet'
|
||||||
|
|
||||||
|
def define_actions(self):
|
||||||
|
"""
|
||||||
|
Defines the UIManager actions.
|
||||||
|
"""
|
||||||
|
self._add_action("AddGramplet", gtk.STOCK_ADD, _("Add a gramplet"))
|
||||||
|
self._add_action("RestoreGramplet", None, _("Restore a gramplet"))
|
||||||
|
|
||||||
def set_inactive(self):
|
def set_inactive(self):
|
||||||
self.active = False
|
self.active = False
|
||||||
self.widget.set_inactive()
|
self.widget.set_inactive()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user