feature #2634 - display regions overtop of images in narrativeweb gallery
svn: r11840
This commit is contained in:
parent
2af0f3a388
commit
2176bbfbf9
@ -2,6 +2,7 @@
|
|||||||
# Gramps - a GTK+/GNOME based genealogy program
|
# Gramps - a GTK+/GNOME based genealogy program
|
||||||
#
|
#
|
||||||
# Copyright (C) 2000-2006 Donald N. Allingham
|
# Copyright (C) 2000-2006 Donald N. Allingham
|
||||||
|
# Copyright (C) 2008-2009 Stephane Charette <stephanecharette@gmail.com>
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or modify
|
# 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
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
@ -12,6 +12,7 @@ dist_pkgdata_DATA = \
|
|||||||
system_filters.xml \
|
system_filters.xml \
|
||||||
tips.xml\
|
tips.xml\
|
||||||
lds.xml\
|
lds.xml\
|
||||||
|
behaviour.css\
|
||||||
Web_Basic-Ash.css\
|
Web_Basic-Ash.css\
|
||||||
Web_Basic-Cypress.css\
|
Web_Basic-Cypress.css\
|
||||||
Web_Basic-Lilac.css\
|
Web_Basic-Lilac.css\
|
||||||
|
@ -481,6 +481,10 @@ table.individuallist tbody tr td.ColumnName a:hover {
|
|||||||
font-weight:normal;
|
font-weight:normal;
|
||||||
}
|
}
|
||||||
#GalleryDisplay {
|
#GalleryDisplay {
|
||||||
|
margin:0 auto;
|
||||||
|
padding:0;
|
||||||
|
position:relative;
|
||||||
|
overflow:hidden;
|
||||||
text-align:center;
|
text-align:center;
|
||||||
}
|
}
|
||||||
#GalleryDisplay img {
|
#GalleryDisplay img {
|
||||||
|
@ -478,6 +478,10 @@ table.individuallist tbody tr td.ColumnName a:hover {
|
|||||||
font-weight:normal;
|
font-weight:normal;
|
||||||
}
|
}
|
||||||
#GalleryDisplay {
|
#GalleryDisplay {
|
||||||
|
margin:0 auto;
|
||||||
|
padding:0;
|
||||||
|
position:relative;
|
||||||
|
overflow:hidden;
|
||||||
text-align:center;
|
text-align:center;
|
||||||
}
|
}
|
||||||
#GalleryDisplay img {
|
#GalleryDisplay img {
|
||||||
|
@ -480,6 +480,10 @@ table.individuallist tbody tr td.ColumnName a:hover {
|
|||||||
font-weight:normal;
|
font-weight:normal;
|
||||||
}
|
}
|
||||||
#GalleryDisplay {
|
#GalleryDisplay {
|
||||||
|
margin:0 auto;
|
||||||
|
padding:0;
|
||||||
|
position:relative;
|
||||||
|
overflow:hidden;
|
||||||
text-align:center;
|
text-align:center;
|
||||||
}
|
}
|
||||||
#GalleryDisplay img {
|
#GalleryDisplay img {
|
||||||
|
@ -481,6 +481,10 @@ table.individuallist tbody tr td.ColumnName a:hover {
|
|||||||
font-weight:normal;
|
font-weight:normal;
|
||||||
}
|
}
|
||||||
#GalleryDisplay {
|
#GalleryDisplay {
|
||||||
|
margin:0 auto;
|
||||||
|
padding:0;
|
||||||
|
position:relative;
|
||||||
|
overflow:hidden;
|
||||||
text-align:center;
|
text-align:center;
|
||||||
}
|
}
|
||||||
#GalleryDisplay img {
|
#GalleryDisplay img {
|
||||||
|
@ -481,6 +481,10 @@ table.individuallist tbody tr td.ColumnName a:hover {
|
|||||||
font-weight:normal;
|
font-weight:normal;
|
||||||
}
|
}
|
||||||
#GalleryDisplay {
|
#GalleryDisplay {
|
||||||
|
margin:0 auto;
|
||||||
|
padding:0;
|
||||||
|
position:relative;
|
||||||
|
overflow:hidden;
|
||||||
text-align:center;
|
text-align:center;
|
||||||
}
|
}
|
||||||
#GalleryDisplay img {
|
#GalleryDisplay img {
|
||||||
|
@ -498,6 +498,10 @@ table.individuallist tbody tr td.ColumnName a {
|
|||||||
font-weight:normal;
|
font-weight:normal;
|
||||||
}
|
}
|
||||||
#GalleryDisplay {
|
#GalleryDisplay {
|
||||||
|
margin:0 auto;
|
||||||
|
padding:0;
|
||||||
|
position:relative;
|
||||||
|
overflow:hidden;
|
||||||
text-align:center;
|
text-align:center;
|
||||||
}
|
}
|
||||||
#GalleryDisplay img {
|
#GalleryDisplay img {
|
||||||
|
@ -514,6 +514,10 @@ table.individuallist tbody tr td.ColumnName a:hover {
|
|||||||
font-weight:normal;
|
font-weight:normal;
|
||||||
}
|
}
|
||||||
#GalleryDisplay {
|
#GalleryDisplay {
|
||||||
|
margin:0 auto;
|
||||||
|
padding:0;
|
||||||
|
position:relative;
|
||||||
|
overflow:hidden;
|
||||||
text-align:center;
|
text-align:center;
|
||||||
}
|
}
|
||||||
#GalleryDisplay img {
|
#GalleryDisplay img {
|
||||||
|
@ -296,6 +296,10 @@ table.surname thead tr th.ColumnParents, table.surname tbody tr td.ColumnParents
|
|||||||
display:none;
|
display:none;
|
||||||
}
|
}
|
||||||
#GalleryDisplay {
|
#GalleryDisplay {
|
||||||
|
margin:0 auto;
|
||||||
|
padding:0;
|
||||||
|
position:relative;
|
||||||
|
overflow:hidden;
|
||||||
text-align:center;
|
text-align:center;
|
||||||
}
|
}
|
||||||
#GalleryDisplay img {
|
#GalleryDisplay img {
|
||||||
|
@ -524,6 +524,10 @@ table.individuallist tbody tr td.ColumnName a:hover {
|
|||||||
font-weight:normal;
|
font-weight:normal;
|
||||||
}
|
}
|
||||||
#GalleryDisplay {
|
#GalleryDisplay {
|
||||||
|
margin:0 auto;
|
||||||
|
padding:0;
|
||||||
|
position:relative;
|
||||||
|
overflow:hidden;
|
||||||
text-align:center;
|
text-align:center;
|
||||||
}
|
}
|
||||||
#GalleryDisplay img {
|
#GalleryDisplay img {
|
||||||
|
89
src/data/behaviour.css
Normal file
89
src/data/behaviour.css
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
/*
|
||||||
|
-------------------------------------------------------------------------------
|
||||||
|
GRAMPS cascading style sheet for common behaviour independant of styles
|
||||||
|
Style Name: n/a (used by many different styles)
|
||||||
|
Style Author: Stephane Charette and Jason Simanek
|
||||||
|
-------------------------------------------------------------------------------
|
||||||
|
GRAMPS is a Free Software Project for Genealogy, offering a professional
|
||||||
|
genealogy program, and a wiki open to all. It is a community project, created,
|
||||||
|
developed and governed by genealogists.
|
||||||
|
|
||||||
|
Go to <http://www.gramps-project.org/> to learn more!
|
||||||
|
|
||||||
|
License
|
||||||
|
-------------------------------------------------------------------------------
|
||||||
|
Copyright 2009 Stephane Charette and Jason Simanek
|
||||||
|
This file is part of the GRAMPS program.
|
||||||
|
|
||||||
|
GRAMPS 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, version 2 of the License.
|
||||||
|
|
||||||
|
GRAMPS 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
|
||||||
|
GRAMPS. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
-------------------------------------------------------------------------------
|
||||||
|
$Id: NWeb-Screen_Basic-Ash.css 11712 2009-01-25 10:00:30Z s_charette $
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* ------------- */
|
||||||
|
/* Image Gallery */
|
||||||
|
/* ------------- */
|
||||||
|
|
||||||
|
/* ensure RegionBox <ol> is hidden and has no margins/padding that would shift the image */
|
||||||
|
ol.RegionBox {
|
||||||
|
display:none;
|
||||||
|
list-style:none;
|
||||||
|
margin:0;
|
||||||
|
padding:0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* show the RegionBox <ol> When the mouse hovers over the gallery */
|
||||||
|
div#GalleryDisplay:hover ol.RegionBox {
|
||||||
|
display:block;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* define how <li> tags should normally look within RegionBox */
|
||||||
|
ol.RegionBox li {
|
||||||
|
margin:0;
|
||||||
|
padding:0;
|
||||||
|
display:block;
|
||||||
|
position:absolute;
|
||||||
|
text-align:center;
|
||||||
|
text-decoration:none;
|
||||||
|
border:dashed 1px #999;
|
||||||
|
background:url(../../../images/blank.gif) repeat;
|
||||||
|
/* IE doesn't work correctly with "hover" if the <li> tag is empty,
|
||||||
|
* so fill the <li> with a blank image; this way the mouse will be
|
||||||
|
* considered in the <li> tag anywhere over the background image
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
/* use a solid border when the mouse hovers over the <li> tags */
|
||||||
|
ol.RegionBox li:hover {
|
||||||
|
z-index:100;
|
||||||
|
border:solid 1px #FFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* links are kept hidden... */
|
||||||
|
ol.RegionBox li a {
|
||||||
|
display:none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ...until we hover over them */
|
||||||
|
ol.RegionBox li:hover a {
|
||||||
|
display:block;
|
||||||
|
text-decoration:none;
|
||||||
|
border-bottom:solid 1px #FFF;
|
||||||
|
background-color:#888;
|
||||||
|
color:#FFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* underline is hidden until we hover over the links */
|
||||||
|
ol.RegionBox li:hover a:hover {
|
||||||
|
text-decoration:underline;
|
||||||
|
}
|
||||||
|
|
@ -54,6 +54,7 @@ map_tile_images = \
|
|||||||
|
|
||||||
dist_pkgdata_DATA = \
|
dist_pkgdata_DATA = \
|
||||||
bad.png\
|
bad.png\
|
||||||
|
blank.gif\
|
||||||
caution.png\
|
caution.png\
|
||||||
good.png\
|
good.png\
|
||||||
gramps.png\
|
gramps.png\
|
||||||
|
BIN
src/images/blank.gif
Normal file
BIN
src/images/blank.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 43 B |
@ -345,6 +345,12 @@ class BasePage:
|
|||||||
(const.PROGRAM_NAME, const.VERSION, const.URL_HOMEPAGE))
|
(const.PROGRAM_NAME, const.VERSION, const.URL_HOMEPAGE))
|
||||||
of.write('\t<meta name="author" content="%s" />\n' % self.author)
|
of.write('\t<meta name="author" content="%s" />\n' % self.author)
|
||||||
|
|
||||||
|
# Link to css behaviours
|
||||||
|
fname = os.path.join("styles", "behaviour.css")
|
||||||
|
url = self.report.build_url_fname(fname, None, self.up)
|
||||||
|
of.write('\t<link href="%s" rel="stylesheet" type="text/css" '
|
||||||
|
'media="screen" />\n' % url)
|
||||||
|
|
||||||
# Link to screen stylesheet
|
# Link to screen stylesheet
|
||||||
fname = os.path.join("styles", self.report.css)
|
fname = os.path.join("styles", self.report.css)
|
||||||
url = self.report.build_url_fname(fname, None, self.up)
|
url = self.report.build_url_fname(fname, None, self.up)
|
||||||
@ -568,7 +574,6 @@ class BasePage:
|
|||||||
of.write('\t\t<h4>%s</h4>\n' % _('Weblinks'))
|
of.write('\t\t<h4>%s</h4>\n' % _('Weblinks'))
|
||||||
of.write('\t\t<ol>\n')
|
of.write('\t\t<ol>\n')
|
||||||
|
|
||||||
index = 1
|
|
||||||
for url in urllist:
|
for url in urllist:
|
||||||
uri = url.get_path()
|
uri = url.get_path()
|
||||||
descr = url.get_description()
|
descr = url.get_description()
|
||||||
@ -583,7 +588,6 @@ class BasePage:
|
|||||||
else:
|
else:
|
||||||
of.write('\t\t\t<li><a href="%s">%s</a>' % (uri, descr))
|
of.write('\t\t\t<li><a href="%s">%s</a>' % (uri, descr))
|
||||||
of.write('</li>\n')
|
of.write('</li>\n')
|
||||||
index = index + 1
|
|
||||||
of.write('\t\t</ol>\n')
|
of.write('\t\t</ol>\n')
|
||||||
of.write('\t</div>\n\n')
|
of.write('\t</div>\n\n')
|
||||||
|
|
||||||
@ -656,14 +660,12 @@ class BasePage:
|
|||||||
key = operator.itemgetter(1),
|
key = operator.itemgetter(1),
|
||||||
cmp = locale.strcoll)
|
cmp = locale.strcoll)
|
||||||
|
|
||||||
index = 1
|
|
||||||
for (path, name, gid) in sortlist:
|
for (path, name, gid) in sortlist:
|
||||||
of.write('\t\t\t<li>')
|
of.write('\t\t\t<li>TEST ')
|
||||||
# Note. 'path' already has a filename extension
|
# Note. 'path' already has a filename extension
|
||||||
url = self.report.build_url_fname(path, None, self.up)
|
url = self.report.build_url_fname(path, None, self.up)
|
||||||
self.person_link(of, url, name, gid)
|
self.person_link(of, url, name, gid)
|
||||||
of.write('</li>\n')
|
of.write('</li>\n')
|
||||||
index = index + 1
|
|
||||||
of.write('\t\t</ol>\n')
|
of.write('\t\t</ol>\n')
|
||||||
of.write('\t</div>\n')
|
of.write('\t</div>\n')
|
||||||
|
|
||||||
@ -1140,6 +1142,73 @@ class MediaPage(BasePage):
|
|||||||
# TODO. How do we pass my_media_list down for use in BasePage?
|
# TODO. How do we pass my_media_list down for use in BasePage?
|
||||||
BasePage.__init__(self, report, title, photo.gramps_id)
|
BasePage.__init__(self, report, title, photo.gramps_id)
|
||||||
|
|
||||||
|
"""
|
||||||
|
*************************************
|
||||||
|
GRAMPS feature #2634 -- attempt to highlight subregions in media
|
||||||
|
objects and link back to the relevant web page.
|
||||||
|
|
||||||
|
This next section of code builds up the "records" we'll need to
|
||||||
|
generate the html/css code to support the subregions
|
||||||
|
*************************************
|
||||||
|
"""
|
||||||
|
|
||||||
|
# TODO, FIXME: Please code review this next block!
|
||||||
|
|
||||||
|
# get all of the backlinks to this media object; meaning all of
|
||||||
|
# the people, events, places, etc..., that use this image
|
||||||
|
_region_items = set()
|
||||||
|
for (classname, newhandle) in db.find_backlink_handles(handle):
|
||||||
|
|
||||||
|
# for each of the backlinks, get the relevant object from the db
|
||||||
|
# and determine a few important things, such as a text name we
|
||||||
|
# can use, and the URL to a relevant web page
|
||||||
|
_obj = None
|
||||||
|
_name = ""
|
||||||
|
_linkurl = "#"
|
||||||
|
if classname == "Person":
|
||||||
|
_obj = db.get_person_from_handle( newhandle )
|
||||||
|
# what is the shortest possible name we could use for this person?
|
||||||
|
_name = _obj.get_primary_name().get_call_name()
|
||||||
|
if not _name or _name == "":
|
||||||
|
_name = _obj.get_primary_name().get_first_name()
|
||||||
|
_linkurl = report.build_url_fname_html(_obj.handle, 'ppl', True)
|
||||||
|
if classname == "Event":
|
||||||
|
_obj = db.get_event_from_handle( newhandle )
|
||||||
|
_name = _obj.get_description()
|
||||||
|
|
||||||
|
# if we found a db object to work with...
|
||||||
|
if _obj:
|
||||||
|
|
||||||
|
# get a list of all media refs for this object
|
||||||
|
medialist = _obj.get_media_list()
|
||||||
|
|
||||||
|
# go media refs looking for one that points to this image
|
||||||
|
for mediaref in medialist:
|
||||||
|
rh = mediaref.get_referenced_handles()
|
||||||
|
(classname, h) = rh[0]
|
||||||
|
|
||||||
|
# if the handles indicate this is a match...
|
||||||
|
if h == handle:
|
||||||
|
|
||||||
|
# get the rectangle (if any) defined in this media ref
|
||||||
|
rectangle = mediaref.get_rectangle()
|
||||||
|
if rectangle:
|
||||||
|
(x1, y1, x2, y2) = rectangle
|
||||||
|
# GRAMPS gives us absolute coordinates,
|
||||||
|
# but we need relative width + height
|
||||||
|
w = x2 - x1
|
||||||
|
h = y2 - y1
|
||||||
|
|
||||||
|
# remember all this information, cause we'll need
|
||||||
|
# need it later when we output the <li>...</li> tags
|
||||||
|
item = (_name, x1, y1, w, h, _linkurl)
|
||||||
|
_region_items.add(item)
|
||||||
|
"""
|
||||||
|
*************************************
|
||||||
|
end of code that looks for and prepares the media object regions
|
||||||
|
*************************************
|
||||||
|
"""
|
||||||
|
|
||||||
of = self.report.create_file(handle, 'img')
|
of = self.report.create_file(handle, 'img')
|
||||||
self.up = True
|
self.up = True
|
||||||
|
|
||||||
@ -1178,8 +1247,10 @@ class MediaPage(BasePage):
|
|||||||
of.write('\t<div id="summaryarea">\n')
|
of.write('\t<div id="summaryarea">\n')
|
||||||
if mime_type:
|
if mime_type:
|
||||||
if mime_type.startswith("image/"):
|
if mime_type.startswith("image/"):
|
||||||
of.write('\t\t<div id="GalleryDisplay">\n')
|
if not target_exists:
|
||||||
if target_exists:
|
of.write('\t\t<div id="GalleryDisplay">\n')
|
||||||
|
of.write('\t\t\t<span class="MissingImage">(%s)</span>' % _("The file has been moved or deleted"))
|
||||||
|
else:
|
||||||
# if the image is spectacularly large, then force the client
|
# if the image is spectacularly large, then force the client
|
||||||
# to resize it, and include a "<a href=" link to the actual
|
# to resize it, and include a "<a href=" link to the actual
|
||||||
# image; most web browsers will dynamically resize an image
|
# image; most web browsers will dynamically resize an image
|
||||||
@ -1188,7 +1259,6 @@ class MediaPage(BasePage):
|
|||||||
(width, height) = ImgManip.image_size(
|
(width, height) = ImgManip.image_size(
|
||||||
Utils.media_path_full(db, photo.get_path()))
|
Utils.media_path_full(db, photo.get_path()))
|
||||||
scale = 1.0
|
scale = 1.0
|
||||||
of.write('\t\t\t')
|
|
||||||
# TODO. Convert disk path to URL.
|
# TODO. Convert disk path to URL.
|
||||||
url = self.report.build_url_fname(newpath, None, self.up)
|
url = self.report.build_url_fname(newpath, None, self.up)
|
||||||
if width > _MAX_IMG_WIDTH or height > _MAX_IMG_HEIGHT:
|
if width > _MAX_IMG_WIDTH or height > _MAX_IMG_HEIGHT:
|
||||||
@ -1196,14 +1266,33 @@ class MediaPage(BasePage):
|
|||||||
scale = min(float(_MAX_IMG_WIDTH)/float(width), float(_MAX_IMG_HEIGHT)/float(height))
|
scale = min(float(_MAX_IMG_WIDTH)/float(width), float(_MAX_IMG_HEIGHT)/float(height))
|
||||||
width = int(width * scale)
|
width = int(width * scale)
|
||||||
height = int(height * scale)
|
height = int(height * scale)
|
||||||
|
of.write('\t\t<div id="GalleryDisplay" style="width:%dpx; height:%dpx;">\n' % (width, height))
|
||||||
|
|
||||||
|
# Feature #2634; display the mouse-selectable regions.
|
||||||
|
# See the large block at the top of this function where
|
||||||
|
# the various regions are stored in _region_items
|
||||||
|
if len(_region_items) > 0:
|
||||||
|
of.write('\t\t\t<ol class="RegionBox">\n')
|
||||||
|
while len(_region_items) > 0:
|
||||||
|
(name, x, y, w, h, linkurl) = _region_items.pop()
|
||||||
|
of.write('\t\t\t\t<li style="'
|
||||||
|
'left:%d%%; '
|
||||||
|
'top:%d%%; '
|
||||||
|
'width:%d%%; '
|
||||||
|
'height:%d%%;">'
|
||||||
|
'<a href="%s">%s</a></li>\n' %
|
||||||
|
(x, y, w, h, linkurl, name))
|
||||||
|
of.write('\t\t\t</ol>\n')
|
||||||
|
|
||||||
|
# display the image
|
||||||
|
of.write('\t\t\t')
|
||||||
|
if scale != 1.0:
|
||||||
of.write('<a href="%s">' % url)
|
of.write('<a href="%s">' % url)
|
||||||
of.write('<img width="%d" height="%d" src="%s" alt="%s" />' % (width, height, url, html_escape(self.page_title)))
|
of.write('<img width="%d" height="%d" src="%s" alt="%s" />' % (width, height, url, html_escape(self.page_title)))
|
||||||
if scale != 1.0:
|
if scale != 1.0:
|
||||||
of.write('</a>')
|
of.write('</a>')
|
||||||
of.write('\n')
|
of.write('\n')
|
||||||
|
|
||||||
else:
|
|
||||||
of.write('\t\t\t<span class="MissingImage">(%s)</span>' % _("The file has been moved or deleted"))
|
|
||||||
of.write('\t\t</div>\n\n')
|
of.write('\t\t</div>\n\n')
|
||||||
else:
|
else:
|
||||||
import tempfile
|
import tempfile
|
||||||
@ -2807,6 +2896,10 @@ class NavWebReport(Report):
|
|||||||
Copy all of the CSS and image files for Narrated Web
|
Copy all of the CSS and image files for Narrated Web
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
# copy behaviour stylesheet
|
||||||
|
fname = os.path.join(const.DATA_DIR, "behaviour.css")
|
||||||
|
self.copy_file(fname, "behaviour.css", "styles")
|
||||||
|
|
||||||
# copy screen stylesheet
|
# copy screen stylesheet
|
||||||
fname = os.path.join(const.DATA_DIR, self.css)
|
fname = os.path.join(const.DATA_DIR, self.css)
|
||||||
self.copy_file(fname, self.css, "styles")
|
self.copy_file(fname, self.css, "styles")
|
||||||
@ -2833,6 +2926,9 @@ class NavWebReport(Report):
|
|||||||
# include GRAMPS favicon
|
# include GRAMPS favicon
|
||||||
imgs += ["favicon.ico"]
|
imgs += ["favicon.ico"]
|
||||||
|
|
||||||
|
# we need the blank image gif neede by behaviour.css
|
||||||
|
imgs += ["blank.gif"]
|
||||||
|
|
||||||
# copy Ancestor Tree graphics if needed???
|
# copy Ancestor Tree graphics if needed???
|
||||||
if self.graph:
|
if self.graph:
|
||||||
imgs += ["Web_Gender_Female.png",
|
imgs += ["Web_Gender_Female.png",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user