Add files from 6a34355fda commit of FastLBRY-GTK notabug.org repo by jyamihud without the flbry/lbrynet binary
649 lines
21 KiB
Python
649 lines
21 KiB
Python
#####################################################################
|
|
# #
|
|
# THIS IS A SOURCE CODE FILE FROM A PROGRAM TO INTERACT WITH THE #
|
|
# LBRY PROTOCOL ( lbry.com ). IT WILL USE THE LBRY SDK ( lbrynet ) #
|
|
# FROM THEIR REPOSITORY ( https://github.com/lbryio/lbry-sdk ) #
|
|
# WHICH I GONNA PRESENT TO YOU AS A BINARY. SINCE I DID NOT DEVELOP #
|
|
# IT AND I'M LAZY TO INTEGRATE IN A MORE SMART WAY. THE SOURCE CODE #
|
|
# OF THE SDK IS AVAILABLE IN THE REPOSITORY MENTIONED ABOVE. #
|
|
# #
|
|
# ALL THE CODE IN THIS REPOSITORY INCLUDING THIS FILE IS #
|
|
# (C) J.Y.Amihud and Other Contributors 2021. EXCEPT THE LBRY SDK. #
|
|
# YOU CAN USE THIS FILE AND ANY OTHER FILE IN THIS REPOSITORY UNDER #
|
|
# THE TERMS OF GNU GENERAL PUBLIC LICENSE VERSION 3 OR ANY LATER #
|
|
# VERSION. TO FIND THE FULL TEXT OF THE LICENSE GO TO THE GNU.ORG #
|
|
# WEBSITE AT ( https://www.gnu.org/licenses/gpl-3.0.html ). #
|
|
# #
|
|
# THE LBRY SDK IS UNFORTUNATELY UNDER THE MIT LICENSE. IF YOU ARE #
|
|
# NOT INTENDING TO USE MY CODE AND JUST THE SDK. YOU CAN FIND IT ON #
|
|
# THEIR OFFICIAL REPOSITORY ABOVE. THEIR LICENSE CHOICE DOES NOT #
|
|
# SPREAD ONTO THIS PROJECT. DON'T GET A FALSE ASSUMPTION THAT SINCE #
|
|
# THEY USE A PUSH-OVER LICENSE, I GONNA DO THE SAME. I'M NOT. #
|
|
# #
|
|
# THE LICENSE CHOSEN FOR THIS PROJECT WILL PROTECT THE 4 ESSENTIAL #
|
|
# FREEDOMS OF THE USER FURTHER, BY NOT ALLOWING ANY WHO TO CHANGE #
|
|
# THE LICENSE AT WILL. SO NO PROPRIETARY SOFTWARE DEVELOPER COULD #
|
|
# TAKE THIS CODE AND MAKE THEIR USER-SUBJUGATING SOFTWARE FROM IT. #
|
|
# #
|
|
#####################################################################
|
|
|
|
|
|
# This file will contain elements used a lot with in the application.
|
|
# They are all built upon GTK, so it's implementable without using these
|
|
# but it may simplify your modification. Since the elements in will be
|
|
# specific to making something like this application easier.
|
|
|
|
import os
|
|
import time
|
|
import urllib.request
|
|
import threading
|
|
import json
|
|
from subprocess import *
|
|
from gi.repository import Gtk
|
|
from gi.repository import Gio
|
|
from gi.repository import Gdk
|
|
from gi.repository import GLib
|
|
from gi.repository import Pango
|
|
from gi.repository import GdkPixbuf
|
|
from PIL import Image, ImageSequence
|
|
|
|
from flbry import url
|
|
|
|
def icon( win, name, f="png"):
|
|
|
|
# This function returns a fitting icon of the current theme,
|
|
# or if not available from another theme on the system.
|
|
# If a custom icon theme is set, it returns the icon from the corresponding folder.
|
|
|
|
if Gtk.IconTheme.get_default().has_icon(name) and win.settings["GTK_icon_theme"] == "System Theme":
|
|
return Gtk.Image.new_from_icon_name(name, Gtk.IconSize.DND)
|
|
else:
|
|
# Real GTK Spinner for loading ? Why not?
|
|
if name == "loading":
|
|
s = Gtk.Spinner()
|
|
s.set_size_request(32,32)
|
|
s.start()
|
|
return s
|
|
|
|
return Gtk.Image.new_from_file("icons/"+win.settings["GTK_icon_theme"]+"/"+name+"."+f)
|
|
|
|
def resize_gif(filename, new_file, size):
|
|
|
|
# This function will resize a gif
|
|
|
|
gif = Image.open(filename)
|
|
layers = ImageSequence.Iterator(gif)
|
|
|
|
def rs(l):
|
|
for i in l:
|
|
rsv = i.copy()
|
|
rsv.thumbnail(size, Image.ANTIALIAS)
|
|
yield rsv
|
|
layers = rs(layers)
|
|
|
|
# Overwrite the original gif
|
|
f = next(layers)
|
|
f.info = gif.info
|
|
f.save(new_file, save_all=True, append_images=list(layers))
|
|
|
|
def load(win, calculation_function, render_function, *args, wait=True):
|
|
|
|
# This function will load widgets that take time to load.
|
|
# Due to the peculiarities of the GTK main thread. I need
|
|
# to separate the computation and the rendering part of
|
|
# the job into two distingt functions.
|
|
|
|
# One will do all the job to get the file or resolve the url
|
|
# or whatever it needs to do, which does not require GTK to
|
|
# be done. The second will be the GTK commands. The rendering,
|
|
# done with in the GTK main thread.
|
|
|
|
wbox = Gtk.HBox()
|
|
widget = icon(win, "loading", "gif")
|
|
wbox.pack_start(widget, True, False, False)
|
|
widget.loaddo = True
|
|
|
|
def resolve_widget_thread(widget, wbox, wf, rf, *args):
|
|
|
|
calculations = wf(*args)
|
|
|
|
def gtk_schedule(calculations, rf):
|
|
# It seems to be important to edit GTK only
|
|
# in the main thread. This will schedule it.
|
|
|
|
new_widget = rf(calculations)
|
|
|
|
widget.destroy()
|
|
wbox.pack_start(new_widget, True, True, True)
|
|
wbox.show_all()
|
|
|
|
|
|
GLib.idle_add(gtk_schedule, calculations, rf)
|
|
|
|
def load_event(w,e):
|
|
if w.loaddo:
|
|
load_thread = threading.Thread(target=resolve_widget_thread, args=(widget, wbox, calculation_function, render_function, *args))
|
|
load_thread.setDaemon(True)
|
|
load_thread.start()
|
|
w.loaddo = False
|
|
if wait:
|
|
widget.connect("draw", load_event)
|
|
else:
|
|
load_event(widget, False)
|
|
|
|
|
|
return wbox
|
|
|
|
image_cache = "/tmp/FastLBRY_GTK_image_cashe/"
|
|
|
|
def image_save_name(url):
|
|
|
|
save_as = ""
|
|
good = "qwertyuiopasdfghjklzxcvbnmQWERTYUIOOPASDFGHJKLZXCVBNM_1234567890"
|
|
for i in url:
|
|
if i in good:
|
|
save_as = save_as + i
|
|
else:
|
|
save_as = save_as + "_"
|
|
try:
|
|
os.mkdir(image_cache)
|
|
except:
|
|
pass
|
|
save_as = image_cache+save_as
|
|
return save_as
|
|
|
|
def clean_image_cache():
|
|
|
|
for i in os.listdir(image_cache):
|
|
os.remove(image_cache+i)
|
|
|
|
def net_image_calculation( url, size_width, size_height, save_as=False, allow_gif=False):
|
|
|
|
ret = ["file", save_as]
|
|
|
|
# This is when we want to load a file
|
|
|
|
if save_as == "FORCELOAD":
|
|
try:
|
|
open(url)
|
|
save_as = url
|
|
except:
|
|
save_as = ""
|
|
|
|
if not save_as:
|
|
save_as = image_save_name(url)
|
|
|
|
# This function will load the image in a separate thread.
|
|
|
|
try:
|
|
open(save_as) # In case it's already been saved
|
|
except Exception as e:
|
|
pass
|
|
|
|
try:
|
|
urllib.request.urlretrieve(url, save_as)
|
|
except Exception as e:
|
|
f = open(save_as, "w")
|
|
f.close()
|
|
|
|
if size_width:
|
|
try:
|
|
|
|
pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size(save_as, size_width, size_height)
|
|
ret = ["pixbuf", pixbuf] #Gtk.Image.new_from_pixbuf(pixbuf)
|
|
except Exception as e:
|
|
|
|
if "image file format" in str(e):
|
|
try:
|
|
PILImage = Image.open(save_as).convert("RGBA")
|
|
PILImage.save(save_as+".png", "png")
|
|
pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size(save_as+".png", size_width, size_height)
|
|
ret = ["pixbuf", pixbuf]
|
|
except:
|
|
ret = ["file", save_as]
|
|
|
|
else:
|
|
try:
|
|
|
|
os.rename(save_as, save_as+".gif")
|
|
resize_gif(save_as+".gif", save_as+"_2.gif", [size_width,size_height])
|
|
ret = ["file", save_as+"_2.gif"] # Gtk.Image.new_from_file(save_as+"_2.gif")
|
|
os.remove(save_as+"_2.gif")
|
|
os.rename(save_as+".gif", save_as)
|
|
|
|
except Exception as e:
|
|
if allow_gif:
|
|
ret = ["file", save_as] #Gtk.Image.new_from_file(save_as)
|
|
|
|
else:
|
|
ret = ["file", save_as] #Gtk.Image.new_from_file(save_as)
|
|
|
|
return ret
|
|
|
|
def net_image_render(calc):
|
|
|
|
# This will make the image itself.
|
|
ret = Gtk.Image()
|
|
if calc[0] == "file":
|
|
ret = Gtk.Image.new_from_file(calc[1])
|
|
elif calc[0] == "pixbuf":
|
|
ret = Gtk.Image.new_from_pixbuf(calc[1])
|
|
|
|
return ret
|
|
|
|
# Converts seconds to time format (H:MM:SS or M:SS)
|
|
def seconds_to_time(seconds=0):
|
|
m, s = divmod(seconds, 60)
|
|
h, m = divmod(m, 60)
|
|
if h:
|
|
return '{:d}:{:02d}:{:02d}'.format(h, m, s)
|
|
else:
|
|
return '{:d}:{:02d}'.format(m, s)
|
|
|
|
def search_item(win, data, channel_load=True, upcoming_streams=True):
|
|
|
|
# This will generate a little item for the claim_search
|
|
|
|
|
|
|
|
box = Gtk.VBox()
|
|
|
|
repost = False
|
|
if "reposted_claim" in data:
|
|
repost = True
|
|
data = data["reposted_claim"]
|
|
|
|
try:
|
|
title = data["value"]["title"]
|
|
except:
|
|
title = data['name']
|
|
|
|
if upcoming_streams and int(data.get("value",[]).get("release_time", 0)) > time.time():
|
|
title = "[ UPCOMING "+time.ctime( int(data.get("value",[]).get("release_time", 0)))+" ] \n" + title
|
|
|
|
try:
|
|
link = data["canonical_url"]
|
|
except:
|
|
link = data["permanent_url"]
|
|
|
|
def public_resolve(w):
|
|
win.url.set_text(link)
|
|
win.url.activate()
|
|
|
|
def new_tab(w, e):
|
|
|
|
if e.get_button()[1] == 2:
|
|
print("MMB")
|
|
win.resolve_tab = "new_tab"
|
|
win.url.set_text(link)
|
|
win.url.activate()
|
|
|
|
namebutton = Gtk.Button()
|
|
namebutton.set_tooltip_text(link)
|
|
namebutton.connect("clicked", public_resolve)
|
|
namebutton.connect("button-press-event", new_tab)
|
|
namebutton.set_relief(Gtk.ReliefStyle.NONE)
|
|
namebutton_box = Gtk.VBox()
|
|
namebutton.add(namebutton_box)
|
|
|
|
try:
|
|
# Trying to get the thumb
|
|
namebutton_thumb = load(win, net_image_calculation, net_image_render, data["value"]["thumbnail"]["url"], 200, 113, "", False)
|
|
namebutton_thumb.set_size_request(200, 113)
|
|
except:
|
|
try:
|
|
# Trying to get a thumb by referencing the mimetype
|
|
namebutton_thumb = icon(win,data["value"]["source"]["media_type"].replace("/", "-"))
|
|
namebutton_thumb.set_size_request(200, 113)
|
|
except:
|
|
namebutton_thumb = icon(win,"none")
|
|
namebutton_thumb.set_size_request(200, 113)
|
|
|
|
# For overlay info on top of thumbnail image
|
|
thumbnail_overlay = Gtk.Overlay()
|
|
overlay_box = Gtk.HBox(spacing=5)
|
|
thumbnail_overlay.add(namebutton_thumb)
|
|
thumbnail_overlay.add_overlay(overlay_box)
|
|
thumbnail_overlay.set_overlay_pass_through(overlay_box, True)
|
|
overlay_box.set_margin_end(25)
|
|
overlay_box.set_margin_bottom(5)
|
|
overlay_box.set_halign(Gtk.Align.END)
|
|
overlay_box.set_valign(Gtk.Align.END)
|
|
|
|
# Prepare info for overlay
|
|
overlay_items_count = 0
|
|
seconds_length = data.get("value", {}) \
|
|
.get("video", {}) \
|
|
.get("duration", "0")
|
|
downloaded_file = url.get_downloaded_file(data.get("claim_id", ""))
|
|
|
|
# Show time
|
|
try:
|
|
if seconds_length != "0":
|
|
overlay_items_count += 1
|
|
time_label = Gtk.Label()
|
|
# For some reason setting text color with CssProvider does not
|
|
# work when loading thumbnail list for a second time. So we use
|
|
# set_markup for text color.
|
|
time_label.set_markup("<span color=\"#EFEFEF\" fgalpha=\"70%\">"+seconds_to_time(seconds_length)+"</span>")
|
|
overlay_box.pack_start(time_label, False, False, 0)
|
|
except:
|
|
pass
|
|
|
|
# Show repost indicator
|
|
try:
|
|
if repost:
|
|
overlay_items_count += 1
|
|
repost_icon = Gtk.Image.new_from_file("icons/repost.svg")
|
|
repost_icon.set_size_request(16, 16)
|
|
overlay_box.pack_start(repost_icon, False, False, 0)
|
|
except:
|
|
pass
|
|
|
|
# Show downloaded indicator
|
|
try:
|
|
if downloaded_file:
|
|
overlay_items_count += 1
|
|
downloaded_icon = Gtk.Image.new_from_file("icons/downloaded.svg")
|
|
downloaded_icon.set_size_request(16, 16)
|
|
overlay_box.pack_start(downloaded_icon, False, False, 0)
|
|
except:
|
|
pass
|
|
|
|
# Finishing up with thumbnail overlay
|
|
thumbnail_overlay.show_all()
|
|
namebutton_box.pack_start(thumbnail_overlay, False, False, 0)
|
|
|
|
if overlay_items_count > 0:
|
|
# Add "thumbnail_overlay" class only when there is something inside
|
|
# the box. Meaning don't add it when empty because padding set on
|
|
# CssProvider makes the empty box show, which is unnecessary.
|
|
overlay_box.get_style_context().add_class("thumbnail_overlay")
|
|
|
|
try:
|
|
title_label = Gtk.Label(title)
|
|
title_label.set_line_wrap_mode( Gtk.WrapMode.WORD )
|
|
title_label.set_line_wrap(True)
|
|
title_label.set_max_width_chars(20)
|
|
title_label.set_xalign(0.0)
|
|
title_label.set_margin_top(5)
|
|
namebutton_box.pack_start(title_label, True,False,0)
|
|
except:
|
|
pass
|
|
|
|
box.pack_start(namebutton, False, False, False)
|
|
|
|
|
|
if "signing_channel" in data and (channel_load or repost):
|
|
box.pack_start(go_to_channel(win, data["signing_channel"]), False, False, False)
|
|
|
|
|
|
##### DRAGING IT OUT THE WINDOW ####
|
|
|
|
def on_drag(widget, drag_context, send, info, time):
|
|
# TODO: swap to FastLBRY HTML instance when ready
|
|
librarian = link.replace("lbry://", win.settings["librarian_instance"])
|
|
librarian = librarian.replace("#", ":")
|
|
send.set_text(librarian, -1)
|
|
|
|
namebutton.drag_source_set(Gdk.ModifierType.BUTTON1_MASK, [], Gdk.DragAction.COPY)
|
|
namebutton.drag_source_add_text_targets()
|
|
namebutton.connect("drag-data-get", on_drag)
|
|
|
|
return box
|
|
|
|
def go_to_channel(win, data, resolve=True):
|
|
try:
|
|
try:
|
|
channel_name = data["value"]["title"]
|
|
except:
|
|
channel_name = data["name"]
|
|
|
|
try:
|
|
channel_url = data["canonical_url"]
|
|
except:
|
|
channel_url = data["name"]
|
|
|
|
try:
|
|
channel_url = channel_url + "#" + data["claim_id"]
|
|
except:
|
|
pass
|
|
|
|
channel_button = Gtk.Button()
|
|
|
|
|
|
if resolve:
|
|
def channel_resolve(w):
|
|
win.url.set_text(channel_url)
|
|
win.url.activate()
|
|
|
|
|
|
try:
|
|
channel_button.set_tooltip_text(data["canonical_url"])
|
|
except:
|
|
channel_button.set_tooltip_text(data["name"])
|
|
channel_button.connect("clicked", channel_resolve)
|
|
|
|
def new_tab(w, e):
|
|
|
|
if e.get_button()[1] == 2:
|
|
print("MMB")
|
|
win.resolve_tab = "new_tab"
|
|
win.url.set_text(channel_url)
|
|
win.url.activate()
|
|
|
|
channel_button.connect("button-press-event", new_tab)
|
|
|
|
channel_button.set_relief(Gtk.ReliefStyle.NONE)
|
|
channel_button_box = Gtk.HBox()
|
|
channel_button.add(channel_button_box)
|
|
|
|
# If channel thumbnail exists.
|
|
try:
|
|
channel_thumb = load(win, net_image_calculation, net_image_render, data["value"]["thumbnail"]["url"], 40, 40, "", False)
|
|
channel_button_box.pack_start(channel_thumb, False,False,False)
|
|
except:
|
|
channel_button_box.pack_start(icon(win, "system-users"), False,False,False)
|
|
title_label = Gtk.Label(" "+channel_name+" ")
|
|
title_label.set_line_wrap_mode( Gtk.WrapMode.WORD )
|
|
title_label.set_line_wrap(True)
|
|
title_label.set_max_width_chars(20)
|
|
channel_button_box.pack_start(title_label, False, False, False)
|
|
|
|
|
|
def on_drag(widget, drag_context, send, info, time):
|
|
# TODO: swap to FastLBRY HTML instance when ready
|
|
librarian = channel_url.replace("lbry://", win.settings["librarian_instance"])
|
|
librarian = librarian.replace("#", ":")
|
|
send.set_text(librarian, -1)
|
|
|
|
channel_button.drag_source_set(Gdk.ModifierType.BUTTON1_MASK, [], Gdk.DragAction.COPY)
|
|
channel_button.drag_source_add_text_targets()
|
|
channel_button.connect("drag-data-get", on_drag)
|
|
|
|
return channel_button
|
|
except Exception as e:
|
|
print("GO TO CHANNEL:", e)
|
|
return Gtk.Label("[anonymous]")
|
|
|
|
|
|
def notify(win, text, subtext="", force=False):
|
|
|
|
|
|
# This function will send a notify send thingy if
|
|
# notifications are set to True
|
|
|
|
enabled = win.settings["notifications"]
|
|
|
|
if enabled and (( not win.is_active()) or force ):
|
|
Popen(["notify-send",
|
|
"-i", os.getcwd()+"/icon.png",
|
|
"-a", "FastLBRY GTK", text, subtext])
|
|
|
|
def select_file(was="", filter=[]):
|
|
|
|
# This is a simple file_chooser_dialog.
|
|
|
|
|
|
|
|
dialog = Gtk.FileChooserDialog("Choose a file",
|
|
None,
|
|
Gtk.FileChooserAction.OPEN,
|
|
(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
|
|
Gtk.STOCK_OPEN, Gtk.ResponseType.OK))
|
|
|
|
# Filter
|
|
if filter:
|
|
filter_sup = Gtk.FileFilter()
|
|
filter_sup.set_name("Supported files")
|
|
for i in filter:
|
|
filter_sup.add_pattern(i)
|
|
dialog.add_filter(filter_sup)
|
|
|
|
filter_any = Gtk.FileFilter()
|
|
filter_any.set_name("All files")
|
|
filter_any.add_pattern("*")
|
|
dialog.add_filter(filter_any)
|
|
|
|
############### PREVIEW CODE ###################
|
|
# TODO:
|
|
# Perhaps more mime-types could be added to it
|
|
# for example .blender could be previewed using
|
|
# blender-thumbnailer.py in each Blender install.
|
|
# Videos by Totem.
|
|
|
|
preview_image= Gtk.Image()
|
|
dialog.set_preview_widget(preview_image)
|
|
def update_preview(dialog):
|
|
path= dialog.get_preview_filename()
|
|
try:
|
|
pixbuf= GdkPixbuf.Pixbuf.new_from_file(path)
|
|
except Exception:
|
|
dialog.set_preview_widget_active(False)
|
|
else:
|
|
maxwidth, maxheight= 300, 700
|
|
width, height= pixbuf.get_width(), pixbuf.get_height()
|
|
scale= min(maxwidth/width, maxheight/height)
|
|
if scale<1:
|
|
width, height= int(width*scale), int(height*scale)
|
|
pixbuf= pixbuf.scale_simple(width, height, GdkPixbuf.InterpType.BILINEAR)
|
|
|
|
preview_image.set_from_pixbuf(pixbuf)
|
|
dialog.set_preview_widget_active(True)
|
|
dialog.connect('update-preview', update_preview)
|
|
|
|
response = dialog.run()
|
|
if response == Gtk.ResponseType.OK:
|
|
ret = dialog.get_filename()
|
|
dialog.destroy()
|
|
return ret
|
|
else:
|
|
dialog.destroy()
|
|
return was
|
|
|
|
def tags_editor(win, data, return_edit_functions=False):
|
|
|
|
|
|
|
|
tagscont = Gtk.HBox()
|
|
|
|
|
|
tagscrl = Gtk.ScrolledWindow()
|
|
tagscrl.set_size_request(40,40)
|
|
tagscont.pack_start(tagscrl, True, True, 0)
|
|
|
|
tagsbox = Gtk.HBox()
|
|
tagscrl.add_with_viewport(tagsbox)
|
|
|
|
def add_tag(tag):
|
|
|
|
if not tag:
|
|
return
|
|
|
|
if tag not in data:
|
|
data.append(tag)
|
|
tagb = Gtk.HBox()
|
|
tagb.pack_start(Gtk.Label(" "+tag+" "), False, False, 0)
|
|
def kill(w):
|
|
tagb.destroy()
|
|
data.remove(tag)
|
|
tagk = Gtk.Button()
|
|
tagk.connect("clicked", kill)
|
|
tagk.set_relief(Gtk.ReliefStyle.NONE)
|
|
tagk.add(icon(win, "edit-delete"))
|
|
tagb.pack_start(tagk, False, False, 0)
|
|
tagb.pack_start(Gtk.VSeparator(), False, False, 5)
|
|
tagsbox.pack_start(tagb, False, False, 0)
|
|
tagsbox.show_all()
|
|
|
|
# Scroll to the last
|
|
def later():
|
|
time.sleep(0.1)
|
|
def now():
|
|
a = tagscrl.get_hadjustment()
|
|
a.set_value(a.get_upper())
|
|
GLib.idle_add(now)
|
|
load_thread = threading.Thread(target=later)
|
|
load_thread.start()
|
|
# The threading is needed, since we want to wait
|
|
# while GTK will update the UI and only then move
|
|
# the adjustent. Becuase else, it will move to the
|
|
# last previous, not to the last last.
|
|
|
|
addt = Gtk.Button()
|
|
addt.set_relief(Gtk.ReliefStyle.NONE)
|
|
addt.add(icon(win, "list-add"))
|
|
tagscont.pack_end(addt, False, False, 0)
|
|
def on_entry(w):
|
|
add_tag(tagentry.get_text())
|
|
tagentry.set_text("")
|
|
tagentry = Gtk.Entry()
|
|
tagentry.connect("activate", on_entry)
|
|
addt.connect("clicked", on_entry)
|
|
tagscont.pack_end(tagentry, False, False, False)
|
|
|
|
|
|
|
|
for tag in data:
|
|
add_tag(tag)
|
|
|
|
if not return_edit_functions:
|
|
return tagscont
|
|
else:
|
|
return tagscont, tagsbox, add_tag
|
|
|
|
def password_entry(win):
|
|
|
|
# This function will create a basic entry for passwords
|
|
# with a button to reveal text.
|
|
|
|
box = Gtk.HBox()
|
|
|
|
entry = Gtk.Entry()
|
|
entry.set_visibility(False)
|
|
|
|
def vis(w, e):
|
|
e.set_visibility(w.get_active())
|
|
|
|
button = Gtk.ToggleButton()
|
|
button.set_relief(Gtk.ReliefStyle.NONE)
|
|
button.add(icon(win, "video-display"))
|
|
button.connect("clicked", vis, entry)
|
|
|
|
box.pack_start(entry, 1,1,2)
|
|
box.pack_end(button, 0,0,1)
|
|
|
|
return box, entry
|
|
|
|
# Show a simple message box with only OK button
|
|
def simple_message_box(primary_text, secondary_text):
|
|
dialog = Gtk.MessageDialog(
|
|
transient_for=None,
|
|
flags=0,
|
|
message_type=Gtk.MessageType.INFO,
|
|
buttons=Gtk.ButtonsType.OK,
|
|
text=primary_text,
|
|
)
|
|
dialog.format_secondary_text(secondary_text)
|
|
dialog.run()
|
|
dialog.destroy()
|