mirror of
https://gitlab.gnome.org/World/Authenticator.git
synced 2025-03-04 08:44:40 +01:00
implement autotools
This commit is contained in:
parent
bf3a4d95ac
commit
15b9fe44b1
23 changed files with 431 additions and 278 deletions
|
@ -1,6 +0,0 @@
|
|||
[default]
|
||||
name=Configuration par défaut
|
||||
device=local
|
||||
runtime=xdg-app:org.gnome.Platform/3.20/x86_64
|
||||
prefix=/app
|
||||
default=true
|
|
@ -1,4 +1,2 @@
|
|||
SUBDIRS = src \
|
||||
data \
|
||||
plugins
|
||||
|
||||
data
|
||||
|
|
|
@ -1,18 +0,0 @@
|
|||
import sqlite3
|
||||
conn = sqlite3.connect('example.db')
|
||||
|
||||
c = conn.cursor()
|
||||
|
||||
# Create table
|
||||
c.execute('''CREATE TABLE providers
|
||||
(date text, trans text, symbol text, qty real, price real)''')
|
||||
|
||||
# Insert a row of data
|
||||
c.execute("INSERT INTO stocks VALUES ('2006-01-05','BUY','RHAT',100,35.14)")
|
||||
|
||||
# Save (commit) the changes
|
||||
conn.commit()
|
||||
|
||||
# We can also close the connection if we are done with it.
|
||||
# Just be sure any changes have been committed or they will be lost.
|
||||
conn.close()
|
|
@ -1,15 +1,26 @@
|
|||
desktopdir = $(DATADIR)/applications
|
||||
desktop_DATA = two-factor.desktop
|
||||
desktop_DATA = twofactorauth.desktop
|
||||
|
||||
UPDATE_DESKTOP = update-desktop-database $(datadir)/applications || :
|
||||
|
||||
uidir = $(pkgdatadir)/data
|
||||
ui_DATA = about.glade
|
||||
ui_DATA = about.glade \
|
||||
shortcuts.glade \
|
||||
menu.glade \
|
||||
style.css
|
||||
|
||||
logodir = $(pkgdatadir)/data
|
||||
logo_DATA = about.glade \
|
||||
shortcuts.glade \
|
||||
menu.glade \
|
||||
style.css
|
||||
|
||||
appdatadir = $(DATADIR)/appdata
|
||||
appdata_DATA = \
|
||||
two-factor.appdata.xml
|
||||
|
||||
EXTRA_DIST = \
|
||||
$(logo_DATA) \
|
||||
$(ui_DATA) \
|
||||
$(appdata_DATA)
|
||||
|
||||
|
|
|
@ -25,7 +25,13 @@
|
|||
<property name="accelerator"><Primary>C</property>
|
||||
</object>
|
||||
</child>
|
||||
|
||||
<child>
|
||||
<object class="GtkShortcutsShortcut">
|
||||
<property name="visible">True</property>
|
||||
<property name="title" translatable="yes" context="shortcut window">Add a new provider</property>
|
||||
<property name="accelerator"><Primary>N</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkShortcutsShortcut">
|
||||
<property name="visible">True</property>
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
background-color: @theme_bg_color;
|
||||
}
|
||||
|
||||
.application-list-row:selected * {
|
||||
.application-list-row:selected GtkImage {
|
||||
color: @theme_fg_selected_color;
|
||||
}
|
||||
|
||||
|
@ -12,13 +12,13 @@
|
|||
}
|
||||
|
||||
.provider-logo-add {
|
||||
margin-left: 15px;
|
||||
margin-bottom: 15px;
|
||||
margin-right: 5px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.application-secret-code {
|
||||
font-size: 10px;
|
||||
margin-top: 0;
|
||||
margin-bottom: 5px;
|
||||
margin-left: 70px;
|
||||
margin-left: 65px;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<application>
|
||||
<id type="desktop">two-factor.desktop</id>
|
||||
<id type="desktop">twofactorauth.desktop</id>
|
||||
<licence>CC0</licence>
|
||||
<name>Two-factor Auth</name>
|
||||
<summary>Two Factor Authentication code generator</summary>
|
BIN
database.db
BIN
database.db
Binary file not shown.
|
@ -1,14 +1,14 @@
|
|||
SUBDIRS = unitsconverter
|
||||
SUBDIRS = twofactorauth
|
||||
|
||||
bin_SCRIPTS = units-converter
|
||||
bin_SCRIPTS = twofactorauth
|
||||
CLEANFILES = $(bin_SCRIPTS)
|
||||
EXTRA_DIST = unit-converter.in
|
||||
EXTRA_DIST = twofactorauth.in
|
||||
|
||||
do_substitution = sed -e 's,[@]pythondir[@],$(pythondir),g' \
|
||||
-e 's,[@]pkgdatadir[@],$(pkgdatadir),g' \
|
||||
-e 's,[@]PACKAGE[@],$(PACKAGE),g' \
|
||||
-e 's,[@]VERSION[@],$(VERSION),g'
|
||||
-e 's,[@]VERSION[@],$(VERSION),g'
|
||||
|
||||
units-converter: units-converter.in Makefile
|
||||
$(do_substitution) < $(srcdir)/units-converter.in > units-converter
|
||||
chmod +x units-converter
|
||||
units-converter: twofactorauth.in Makefile
|
||||
$(do_substitution) < $(srcdir)/twofactorauth.in > twofactorauth
|
||||
chmod +x twofactorauth
|
||||
|
|
|
@ -20,10 +20,10 @@
|
|||
"""
|
||||
import sys
|
||||
sys.path.insert(1, '@pythondir@')
|
||||
from twofactor import application
|
||||
from twofactorauth import application
|
||||
|
||||
if __name__ == "__main__":
|
||||
app = application.UnitsConverter(package="@PACKAGE@",
|
||||
app = application.TwoFactor(package="@PACKAGE@",
|
||||
version="@VERSION@",
|
||||
pkgdatadir="@pkgdatadir@")
|
||||
app.run(None)
|
8
src/twofactorauth/Makefile.am
Normal file
8
src/twofactorauth/Makefile.am
Normal file
|
@ -0,0 +1,8 @@
|
|||
SUBDIRS = models \
|
||||
ui
|
||||
|
||||
app_PYTHON = \
|
||||
application.py \
|
||||
__init__.py
|
||||
|
||||
appdir = $(pythondir)/TwoFactorAuth
|
0
src/twofactorauth/__init__.py
Normal file
0
src/twofactorauth/__init__.py
Normal file
|
@ -1,6 +1,6 @@
|
|||
from gi import require_version
|
||||
require_version("Gtk", "3.0")
|
||||
from gi.repository import Gtk, GLib, Gio, Gdk
|
||||
from gi.repository import Gtk, GLib, Gio, Gdk, GObject
|
||||
from ui.window import TwoFactorWindow
|
||||
import logging
|
||||
from models.provider import Provider
|
||||
|
@ -9,19 +9,19 @@ logging.basicConfig(level=logging.DEBUG,
|
|||
format='[%(levelname)s] %(message)s',
|
||||
)
|
||||
|
||||
# TODO : https://pypi.python.org/pypi/pyotp
|
||||
|
||||
|
||||
class TwoFactor(Gtk.Application):
|
||||
win = None
|
||||
|
||||
def __init__(self):
|
||||
def __init__(self, *args, **kwargs):
|
||||
for key in kwargs:
|
||||
setattr(self, key, kwargs[key])
|
||||
Gtk.Application.__init__(self,
|
||||
application_id='org.gnome.twofactor',
|
||||
application_id='org.gnome.twofactorauth',
|
||||
flags=Gio.ApplicationFlags.FLAGS_NONE)
|
||||
GLib.set_application_name("Two-factor")
|
||||
GLib.set_prgname('twofactor')
|
||||
|
||||
GLib.set_application_name("Two-Factor Auth")
|
||||
GLib.set_prgname('two_factor_auth')
|
||||
GObject.threads_init()
|
||||
provider = Gtk.CssProvider()
|
||||
css_file = "/home/bilal/Projects/Two-factor-gtk/data/style.css"
|
||||
try:
|
||||
|
@ -29,10 +29,10 @@ class TwoFactor(Gtk.Application):
|
|||
Gtk.StyleContext.add_provider_for_screen(Gdk.Screen.get_default(),
|
||||
provider,
|
||||
Gtk.STYLE_PROVIDER_PRIORITY_USER)
|
||||
logging.debug("[CSS]: Loading css file %s" % css_file)
|
||||
logging.debug("Loading css file %s" % css_file)
|
||||
except Exception as e:
|
||||
logging.debug("[CSS]: File not found %s" % css_file)
|
||||
logging.debug("[CSS]: Error message %s" % str(e))
|
||||
logging.debug("File not found %s" % css_file)
|
||||
logging.debug("Error message %s" % str(e))
|
||||
|
||||
def do_startup(self):
|
||||
Gtk.Application.do_startup(self)
|
||||
|
@ -60,6 +60,7 @@ class TwoFactor(Gtk.Application):
|
|||
TwoFactorWindow(self)
|
||||
self.win.show()
|
||||
self.add_window(self.win)
|
||||
self.get_active_window().present()
|
||||
|
||||
def on_shortcuts(self, *args):
|
||||
logging.debug("Shortcuts window")
|
||||
|
@ -70,11 +71,12 @@ class TwoFactor(Gtk.Application):
|
|||
self.win.show_about()
|
||||
|
||||
def on_quit(self, *args):
|
||||
Gtk.main_quit()
|
||||
self.win.destroy()
|
||||
# Clear the clipboard once the application is closed, for safety resasons
|
||||
try:
|
||||
clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD)
|
||||
clipboard.clear()
|
||||
except Exception as e:
|
||||
logging.error(str(e))
|
||||
for win in self.get_windows():
|
||||
win.destroy()
|
||||
self.quit()
|
||||
|
||||
|
||||
|
||||
app = TwoFactor()
|
||||
app.run(None)
|
5
src/twofactorauth/models/Makefile.am
Normal file
5
src/twofactorauth/models/Makefile.am
Normal file
|
@ -0,0 +1,5 @@
|
|||
app_PYTHON = \
|
||||
code.py \
|
||||
provider.py
|
||||
|
||||
appdir = $(pythondir)/TwoFactorAuth/models
|
34
src/twofactorauth/models/code.py
Normal file
34
src/twofactorauth/models/code.py
Normal file
|
@ -0,0 +1,34 @@
|
|||
from pyotp import TOTP
|
||||
import logging
|
||||
logging.basicConfig(level=logging.DEBUG,
|
||||
format='[%(levelname)s] %(message)s',
|
||||
)
|
||||
class Code:
|
||||
password = None
|
||||
label = None
|
||||
|
||||
def __init__(self, secret_code):
|
||||
self.secret_code = secret_code
|
||||
self.create()
|
||||
|
||||
def create(self):
|
||||
try:
|
||||
self.totp = TOTP(self.secret_code)
|
||||
self.password = self.totp.now()
|
||||
except Exception as e:
|
||||
logging.error("Canno't generate a two-factor auth code")
|
||||
logging.error(str(e))
|
||||
def update(self):
|
||||
self.password = self.totp.now()
|
||||
|
||||
|
||||
def get_secret_code(self):
|
||||
try:
|
||||
if self.password:
|
||||
return self.password
|
||||
else:
|
||||
raise AttributeError
|
||||
except AttributeError as e:
|
||||
logging.error("Couldn't generate the code")
|
||||
logging.error(str(e))
|
||||
return None
|
|
@ -15,7 +15,6 @@ class Provider:
|
|||
try:
|
||||
self.conn.execute(query, t)
|
||||
self.conn.commit()
|
||||
logging.debug("Proivder '%s' added to the database" % name)
|
||||
except Exception as e:
|
||||
logging.error(query)
|
||||
logging.error("Couldn't add a new provider to database")
|
||||
|
@ -25,7 +24,7 @@ class Provider:
|
|||
query = "DELETE FROM providers WHERE id=?"
|
||||
try:
|
||||
self.conn.execute(query, (id,))
|
||||
self.conn.commit()
|
||||
self.conn.commit()
|
||||
except Exception as e:
|
||||
logging.error("Couldn't remove the application with id : %s", id)
|
||||
logging.error(str(e))
|
||||
|
@ -35,7 +34,6 @@ class Provider:
|
|||
query = "SELECT COUNT(id) AS count FROM providers"
|
||||
try:
|
||||
data = c.execute(query)
|
||||
logging.debug("Providers list fetched successfully")
|
||||
return data.fetchone()[0]
|
||||
except Exception as e:
|
||||
logging.error(query)
|
||||
|
@ -48,7 +46,6 @@ class Provider:
|
|||
query = "SELECT * FROM providers"
|
||||
try:
|
||||
data = c.execute(query)
|
||||
logging.debug("Providers list fetched successfully")
|
||||
return data.fetchall()
|
||||
except Exception as e:
|
||||
logging.error(query)
|
||||
|
@ -59,12 +56,16 @@ class Provider:
|
|||
def get_provider_image(self, image):
|
||||
img = Gtk.Image(xalign=0)
|
||||
directory = "/home/bilal/Projects/Two-factor-gtk/data/logos/"
|
||||
image = directory + image
|
||||
if path.exists(image):
|
||||
theme = Gtk.IconTheme.get_default()
|
||||
if path.isfile(directory + image) and path.exists(directory + image):
|
||||
img.set_from_file(directory + image)
|
||||
elif path.isfile(image) and path.exists(image):
|
||||
img.set_from_file(image)
|
||||
elif theme.has_icon(path.splitext(image)[0]):
|
||||
img.set_from_icon_name(path.splitext(image)[0],
|
||||
Gtk.IconSize.DIALOG)
|
||||
else:
|
||||
img.set_from_icon_name(image,
|
||||
Gtk.IconSize.DIALOG)
|
||||
img.set_from_icon_name("image-missing", Gtk.IconSize.DIALOG)
|
||||
return img
|
||||
|
||||
def get_latest_id(self):
|
8
src/twofactorauth/ui/Makefile.am
Normal file
8
src/twofactorauth/ui/Makefile.am
Normal file
|
@ -0,0 +1,8 @@
|
|||
app_PYTHON = \
|
||||
add_provider.py \
|
||||
confirmation.py \
|
||||
listrow.py \
|
||||
logo_provider.py \
|
||||
window.py
|
||||
|
||||
appdir = $(pythondir)/TwoFactorAuth/ui
|
|
@ -30,9 +30,9 @@ class AddProviderWindow(Gtk.Window):
|
|||
Gtk.Window.__init__(self, title="Add a new provider", modal=True,
|
||||
destroy_with_parent=True)
|
||||
self.connect("delete-event", lambda x, y: self.destroy())
|
||||
self.resize(350, 100)
|
||||
self.resize(300, 100)
|
||||
self.set_border_width(18)
|
||||
self.set_size_request(350, 100)
|
||||
self.set_size_request(300, 100)
|
||||
self.set_position(Gtk.WindowPosition.CENTER)
|
||||
self.set_resizable(False)
|
||||
self.set_transient_for(self.parent)
|
||||
|
@ -55,7 +55,11 @@ class AddProviderWindow(Gtk.Window):
|
|||
secret_entry,
|
||||
image_entry)
|
||||
id = self.parent.app.provider.get_latest_id()
|
||||
self.parent.refresh_window()
|
||||
print()
|
||||
if self.parent.app.provider.count_providers() == 1:
|
||||
self.parent.refresh_window(True)
|
||||
else:
|
||||
self.parent.update_list(id, name_entry, secret_entry, image_entry)
|
||||
self.close_window()
|
||||
except Exception as e:
|
||||
logging.error("Error in adding a new provider")
|
||||
|
@ -82,10 +86,8 @@ class AddProviderWindow(Gtk.Window):
|
|||
hbox_two_factor.pack_end(two_factor_entry, False, True, 0)
|
||||
|
||||
logo_event = Gtk.EventBox()
|
||||
logo_image = Gtk.Image()
|
||||
logo_image.set_from_icon_name("image-missing",
|
||||
Gtk.IconSize.DIALOG)
|
||||
logo_event.get_style_context().add_class("provider-logo-add")
|
||||
logo_image = self.parent.app.provider.get_provider_image("image-missing")
|
||||
logo_image.get_style_context().add_class("provider-logo-add")
|
||||
logo_event.add(logo_image)
|
||||
logo_event.connect("button-press-event", self.select_logo)
|
||||
logo_box.pack_start(logo_event, False, False, 6)
|
155
src/twofactorauth/ui/listrow.py
Normal file
155
src/twofactorauth/ui/listrow.py
Normal file
|
@ -0,0 +1,155 @@
|
|||
|
||||
from gi import require_version
|
||||
require_version("Gtk", "3.0")
|
||||
from gi.repository import Gtk, GLib, Gio, Gdk, GObject
|
||||
from models.code import Code
|
||||
from threading import Thread
|
||||
import time
|
||||
|
||||
import logging
|
||||
from math import pi
|
||||
|
||||
class ListBoxRow(Thread):
|
||||
counter_max = 30
|
||||
counter = 30
|
||||
code = None
|
||||
code_generated = True
|
||||
|
||||
def __init__(self, parent, id, name, secret_code, logo):
|
||||
Thread.__init__(self)
|
||||
self.parent = parent
|
||||
self.id = id
|
||||
self.name = name
|
||||
self.secret_code = secret_code
|
||||
self.code = Code(secret_code)
|
||||
self.logo = logo
|
||||
self.create_row()
|
||||
self.start()
|
||||
GObject.timeout_add_seconds(1, self.refresh_listbox)
|
||||
|
||||
|
||||
def on_button_press_event(self, widget, event) :
|
||||
if event.button == Gdk.EventType._2BUTTON_PRESS:
|
||||
#data = widget.get_path_at_pos(int(event.x), int(event.y))
|
||||
print("helloooo")
|
||||
|
||||
def create_row(self):
|
||||
self.row = Gtk.ListBoxRow()
|
||||
self.row.get_style_context().add_class("application-list-row")
|
||||
self.row.connect("button-press-event", self.on_button_press_event)
|
||||
vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
|
||||
hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
|
||||
pass_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
|
||||
pass_box.set_visible(False)
|
||||
vbox.pack_start(hbox, True, True, 6)
|
||||
vbox.pack_start(pass_box, True, True, 6)
|
||||
|
||||
# ID
|
||||
label_id = Gtk.Label()
|
||||
label_id.set_text(str(self.id))
|
||||
label_id.set_visible(False)
|
||||
label_id.set_no_show_all(True)
|
||||
vbox.pack_end(label_id, False, False, 0)
|
||||
|
||||
# Checkbox
|
||||
checkbox = Gtk.CheckButton()
|
||||
checkbox.set_visible(False)
|
||||
checkbox.set_no_show_all(True)
|
||||
checkbox.connect("toggled", self.parent.select_application)
|
||||
hbox.pack_start(checkbox, False, True, 6)
|
||||
|
||||
# Provider logo
|
||||
provider_logo = self.parent.app.provider.get_provider_image(self.logo)
|
||||
hbox.pack_start(provider_logo, False, True, 6)
|
||||
|
||||
# Provider name
|
||||
application_name = Gtk.Label(xalign=0)
|
||||
application_name.get_style_context().add_class("application-name")
|
||||
application_name.set_text(self.name)
|
||||
hbox.pack_start(application_name, True, True, 6)
|
||||
# Copy button
|
||||
copy_event = Gtk.EventBox()
|
||||
copy_button = Gtk.Image(xalign=0)
|
||||
copy_button.set_from_icon_name("edit-copy-symbolic",
|
||||
Gtk.IconSize.SMALL_TOOLBAR)
|
||||
copy_button.set_tooltip_text("Copy the generated code..")
|
||||
copy_event.connect("button-press-event", self.parent.copy_code)
|
||||
copy_event.add(copy_button)
|
||||
hbox.pack_end(copy_event, False, True, 6)
|
||||
|
||||
# Remove button
|
||||
remove_event = Gtk.EventBox()
|
||||
remove_button = Gtk.Image(xalign=0)
|
||||
remove_button.set_from_icon_name("list-remove-symbolic",
|
||||
Gtk.IconSize.SMALL_TOOLBAR)
|
||||
remove_button.set_tooltip_text("Remove the source..")
|
||||
remove_event.add(remove_button)
|
||||
remove_event.connect("button-press-event", self.parent.remove_provider)
|
||||
hbox.pack_end(remove_event, False, True, 6)
|
||||
|
||||
self.darea = Gtk.DrawingArea()
|
||||
self.darea.set_size_request(24, 24)
|
||||
|
||||
code_label = Gtk.Label(xalign=0)
|
||||
code_label.get_style_context().add_class("application-secret-code")
|
||||
# TODO : show the real secret code
|
||||
self.update_code(code_label)
|
||||
pass_box.set_no_show_all(True)
|
||||
pass_box.pack_end(self.darea, False, True, 6)
|
||||
pass_box.pack_start(code_label, False, True, 6)
|
||||
|
||||
self.row.add(vbox)
|
||||
|
||||
|
||||
def get_counter(self):
|
||||
return self.counter
|
||||
|
||||
def run(self):
|
||||
while self.code_generated:
|
||||
self.counter -= 1
|
||||
if self.counter < 0:
|
||||
self.counter = self.counter_max
|
||||
self.regenerate_code()
|
||||
self.darea.connect("draw", self.expose)
|
||||
self.row.changed()
|
||||
time.sleep(1)
|
||||
|
||||
def get_listrow(self):
|
||||
return self.row
|
||||
|
||||
def refresh_listbox(self):
|
||||
self.parent.listbox.hide()
|
||||
self.parent.listbox.show_all()
|
||||
return self.code_generated
|
||||
|
||||
def regenerate_code(self):
|
||||
label = self.row.get_children()[0].get_children()[1].get_children()[0]
|
||||
if label:
|
||||
self.code.update()
|
||||
self.update_code(label)
|
||||
|
||||
def update_code(self, label):
|
||||
try:
|
||||
label.set_text(self.code.get_secret_code())
|
||||
except TypeError as e:
|
||||
logging.error("Canno't generate secret code")
|
||||
logging.error(str(e))
|
||||
label.set_text("Couldn't generate the secret code")
|
||||
self.code_generated = False
|
||||
|
||||
def expose(self, darea, cairo):
|
||||
try:
|
||||
if self.code_generated:
|
||||
cairo.arc(12, 12, 12, 0, (self.counter*2*pi/self.counter_max))
|
||||
cairo.set_source_rgba(0, 0, 0, 0.4)
|
||||
cairo.fill_preserve()
|
||||
if self.counter < self.counter_max/2:
|
||||
cairo.set_source_rgb(0, 0, 0)
|
||||
else:
|
||||
cairo.set_source_rgb(1, 1, 1)
|
||||
cairo.move_to(8, 15)
|
||||
cairo.show_text(str(self.counter))
|
||||
# self.draw(cairo)
|
||||
except Exception as e:
|
||||
logging.error(str(e))
|
||||
return False
|
|
@ -9,6 +9,9 @@ logging.basicConfig(level=logging.DEBUG,
|
|||
class LogoProviderWindow(Gtk.Window):
|
||||
|
||||
def __init__(self, window):
|
||||
directory = "/home/bilal/Projects/Two-factor-gtk/data/logos/"
|
||||
self.logos = listdir(directory)
|
||||
self.logos.sort()
|
||||
self.window = window
|
||||
self.generate_window()
|
||||
self.genereate_searchbar()
|
||||
|
@ -31,7 +34,7 @@ class LogoProviderWindow(Gtk.Window):
|
|||
|
||||
def filter_func(self, row, data, notify_destroy):
|
||||
provider_label = row.get_children()[0].get_children()[0].get_children()
|
||||
data = data.strip()
|
||||
data = data.strip().lower()
|
||||
if len(data) > 0:
|
||||
return data in provider_label[1].get_text().lower()
|
||||
else:
|
||||
|
@ -39,6 +42,12 @@ class LogoProviderWindow(Gtk.Window):
|
|||
|
||||
def filter_providers(self, entry):
|
||||
data = entry.get_text()
|
||||
if len(data) != 0:
|
||||
entry.set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY,
|
||||
"edit-clear-symbolic")
|
||||
else:
|
||||
entry.set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY,
|
||||
None)
|
||||
self.listbox.set_filter_func(self.filter_func, data, False)
|
||||
|
||||
def on_key_press(self, provider, keyevent):
|
||||
|
@ -54,7 +63,7 @@ class LogoProviderWindow(Gtk.Window):
|
|||
search_box.set_visible(is_visible)
|
||||
search_box.show_all()
|
||||
if is_visible:
|
||||
search_box.get_children()[1].grab_focus_without_selecting()
|
||||
search_box.get_children()[0].grab_focus_without_selecting()
|
||||
else:
|
||||
self.listbox.set_filter_func(lambda x,y,z : True, None, False)
|
||||
elif keypressed == "return":
|
||||
|
@ -62,40 +71,29 @@ class LogoProviderWindow(Gtk.Window):
|
|||
|
||||
def genereate_searchbar(self):
|
||||
hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
|
||||
hbox.set_margin_left(40)
|
||||
|
||||
search_image = Gtk.Image(xalign=0)
|
||||
search_image.set_from_icon_name("system-search-symbolic",
|
||||
Gtk.IconSize.SMALL_TOOLBAR)
|
||||
search_image.set_tooltip_text("Type to search")
|
||||
hbox.set_margin_left(60)
|
||||
|
||||
search_entry = Gtk.Entry()
|
||||
search_entry.connect("changed", self.filter_providers)
|
||||
search_entry.set_icon_from_icon_name(Gtk.EntryIconPosition.PRIMARY,
|
||||
"system-search-symbolic")
|
||||
|
||||
hbox.pack_start(search_image, False, True, 6)
|
||||
hbox.pack_start(search_entry, False, True, 6)
|
||||
hbox.set_visible(False)
|
||||
self.get_children()[0].pack_start(hbox, False, True, 0)
|
||||
self.get_children()[0].pack_start(hbox, False, True, 6)
|
||||
self.get_children()[0].get_children()[0].set_no_show_all(True)
|
||||
|
||||
def select_logo(self, *args):
|
||||
index = self.listbox.get_selected_row().get_index()
|
||||
directory = "/home/bilal/Projects/Two-factor-gtk/data/logos/"
|
||||
files = listdir(directory)
|
||||
files.sort()
|
||||
count = len(files)
|
||||
if count > 0:
|
||||
img_path = files[index]
|
||||
if len(self.logos) > 0:
|
||||
img_path = self.logos[index]
|
||||
self.window.update_logo(img_path)
|
||||
self.close_window()
|
||||
|
||||
def generate_compenents(self):
|
||||
box_outer = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6)
|
||||
directory = "/home/bilal/Projects/Two-factor-gtk/data/logos/"
|
||||
files = listdir(directory)
|
||||
files.sort()
|
||||
count = len(files)
|
||||
if count > 0:
|
||||
if len(self.logos) > 0:
|
||||
# Create a ScrolledWindow for installed applications
|
||||
scrolled_win = Gtk.ScrolledWindow()
|
||||
scrolled_win.add_with_viewport(box_outer)
|
||||
|
@ -107,8 +105,8 @@ class LogoProviderWindow(Gtk.Window):
|
|||
self.listbox.set_selection_mode(Gtk.SelectionMode.SINGLE)
|
||||
box_outer.pack_start(self.listbox, True, True, 0)
|
||||
i = 0
|
||||
while i < count:
|
||||
logo = files[i]
|
||||
while i < len(self.logos):
|
||||
logo = self.logos[i]
|
||||
provider_name = path.splitext(logo)[0].strip(".").title()
|
||||
row = Gtk.ListBoxRow()
|
||||
row.get_style_context().add_class("application-list-row")
|
||||
|
@ -116,11 +114,8 @@ class LogoProviderWindow(Gtk.Window):
|
|||
hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
|
||||
|
||||
# Provider logo
|
||||
provider_logo = Gtk.Image(xalign=0)
|
||||
provider_logo.set_from_file(directory + logo)
|
||||
#provider_logo.scale_simple(Gtk.IconSize.DIALOG,
|
||||
# Gtk.IconSize.DIALOG,
|
||||
# GdkPixbuf.InterpType.BILINEAR)
|
||||
provider = self.window.parent.app.provider
|
||||
provider_logo = provider.get_provider_image(logo)
|
||||
hbox.pack_start(provider_logo, False, True, 6)
|
||||
|
||||
# Provider name
|
|
@ -1,19 +1,21 @@
|
|||
|
||||
from gi import require_version
|
||||
require_version("Gtk", "3.0")
|
||||
from gi.repository import Gtk, GLib, Gio, Gdk
|
||||
from gi.repository import Gtk, GLib, Gio, Gdk, GObject
|
||||
from ui.add_provider import AddProviderWindow
|
||||
from ui.confirmation import ConfirmationMessage
|
||||
import logging
|
||||
from ui.listrow import ListBoxRow
|
||||
from threading import Thread
|
||||
|
||||
import logging
|
||||
from math import pi
|
||||
logging.basicConfig(level=logging.DEBUG,
|
||||
format='[%(levelname)s] %(message)s',
|
||||
)
|
||||
|
||||
class TwoFactorWindow(Gtk.Window):
|
||||
class TwoFactorWindow(Gtk.ApplicationWindow):
|
||||
app = None
|
||||
selected_app_idx = None
|
||||
checkboxes = []
|
||||
|
||||
def __init__(self, application):
|
||||
self.app = application
|
||||
|
@ -24,15 +26,16 @@ class TwoFactorWindow(Gtk.Window):
|
|||
self.get_children()[0].get_children()[0].set_visible(False)
|
||||
|
||||
def generate_window(self, *args):
|
||||
Gtk.Window.__init__(self, application=self.app)
|
||||
self.connect("delete-event", lambda x, y: self.app.quit())
|
||||
Gtk.ApplicationWindow.__init__(self, Gtk.WindowType.TOPLEVEL,
|
||||
application=self.app)
|
||||
self.set_position(Gtk.WindowPosition.CENTER)
|
||||
self.set_wmclass("twofactor", "Two-Factor")
|
||||
self.set_wmclass("two_factor_auth", "Two-Factor Auth")
|
||||
self.resize(350, 500)
|
||||
self.set_size_request(350, 500)
|
||||
self.set_resizable(False)
|
||||
self.connect("key_press_event", self.on_key_press)
|
||||
self.app.win = self
|
||||
self.connect("delete-event", lambda x, y: self.app.on_quit())
|
||||
self.add(Gtk.Box(orientation=Gtk.Orientation.VERTICAL))
|
||||
|
||||
def on_key_press(self, provider, keyevent):
|
||||
|
@ -44,17 +47,20 @@ class TwoFactorWindow(Gtk.Window):
|
|||
elif keypressed == "f":
|
||||
if keyevent.state == CONTROL_MASK:
|
||||
if self.app.provider.count_providers() > 0:
|
||||
search_box = self.get_children()[0].get_children()[0]
|
||||
search_box = self.get_children()[0].get_children()[0].get_children()[0]
|
||||
is_visible = search_box.get_no_show_all()
|
||||
search_box.set_no_show_all(not is_visible)
|
||||
search_box.set_visible(is_visible)
|
||||
search_box.show_all()
|
||||
if is_visible:
|
||||
search_box.get_children()[1].grab_focus_without_selecting()
|
||||
search_box.get_children()[0].grab_focus_without_selecting()
|
||||
else:
|
||||
self.listbox.set_filter_func(lambda x,y,z : True, None, False)
|
||||
elif keypressed == "n":
|
||||
if keyevent.state == CONTROL_MASK:
|
||||
self.add_provider()
|
||||
elif keypressed == "delete":
|
||||
self.remove_application()
|
||||
self.remove_provider()
|
||||
elif keypressed == "return":
|
||||
if self.app.provider.count_providers() > 0:
|
||||
if self.listbox.get_selected_row():
|
||||
|
@ -71,40 +77,42 @@ class TwoFactorWindow(Gtk.Window):
|
|||
|
||||
def filter_providers(self, entry):
|
||||
data = entry.get_text()
|
||||
if len(data) != 0:
|
||||
entry.set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY,
|
||||
"edit-clear-symbolic")
|
||||
else:
|
||||
entry.set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY,
|
||||
None)
|
||||
self.listbox.set_filter_func(self.filter_func, data, False)
|
||||
|
||||
def genereate_searchbar(self):
|
||||
hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
|
||||
hbox.set_margin_left(40)
|
||||
|
||||
search_image = Gtk.Image(xalign=0)
|
||||
search_image.set_from_icon_name("system-search-symbolic",
|
||||
Gtk.IconSize.SMALL_TOOLBAR)
|
||||
search_image.set_tooltip_text("Type to search")
|
||||
|
||||
hbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
|
||||
search_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
|
||||
search_entry = Gtk.Entry()
|
||||
search_box.set_margin_left(60)
|
||||
search_entry.set_width_chars(21)
|
||||
search_entry.connect("changed", self.filter_providers)
|
||||
search_entry.set_icon_from_icon_name(Gtk.EntryIconPosition.PRIMARY,
|
||||
"system-search-symbolic")
|
||||
|
||||
hbox.pack_start(search_image, False, True, 6)
|
||||
hbox.pack_start(search_entry, False, True, 6)
|
||||
search_box.pack_start(search_entry, False, True, 0)
|
||||
hbox.pack_start(search_box, False, True, 6)
|
||||
hbox.set_visible(False)
|
||||
self.get_children()[0].pack_start(hbox, False, True, 6)
|
||||
self.get_children()[0].get_children()[0].set_no_show_all(True)
|
||||
|
||||
self.get_children()[0].pack_start(hbox, True, True, 0)
|
||||
search_box.set_no_show_all(True)
|
||||
|
||||
def remove_selected(self, *args):
|
||||
i = 0
|
||||
confirmation = ConfirmationMessage(self, "Are you sure??")
|
||||
message = "Do you really want to remove the two-factor auth provider?"
|
||||
confirmation = ConfirmationMessage(self, message)
|
||||
confirmation.show()
|
||||
if confirmation.get_confirmation():
|
||||
while i < len(self.checkboxes):
|
||||
if self.checkboxes[i].get_active():
|
||||
selected_row = self.listbox.get_row_at_index(i)
|
||||
label_id = selected_row.get_children()[0].get_children()[2]
|
||||
self.app.provider.remove_from_database(int(label_id.get_text()))
|
||||
self.listbox.remove(selected_row)
|
||||
del self.checkboxes[i]
|
||||
i += 1
|
||||
for row in self.listbox.get_children():
|
||||
checkbox = self.get_checkbox_from_row(row)
|
||||
if checkbox.get_active():
|
||||
label_id = row.get_children()[0].get_children()[2]
|
||||
label_id = int(label_id.get_text())
|
||||
self.app.provider.remove_from_database(label_id)
|
||||
self.listbox.remove(row)
|
||||
self.listbox.unselect_all()
|
||||
confirmation.destroy()
|
||||
self.refresh_window()
|
||||
|
@ -174,14 +182,14 @@ class TwoFactorWindow(Gtk.Window):
|
|||
listrow_box = self.listbox.get_row_at_index(index)
|
||||
self.listbox.select_row(listrow_box)
|
||||
|
||||
while i < len(self.checkboxes):
|
||||
visible = self.checkboxes[i].get_visible()
|
||||
selected = self.checkboxes[i].get_active()
|
||||
for row in self.listbox.get_children():
|
||||
checkbox = self.get_checkbox_from_row(row)
|
||||
visible = checkbox.get_visible()
|
||||
selected = checkbox.get_active()
|
||||
if not button_visible:
|
||||
self.select_application(self.checkboxes[i])
|
||||
self.checkboxes[i].set_visible(not visible)
|
||||
self.checkboxes[i].set_no_show_all(visible)
|
||||
i += 1
|
||||
self.select_application(checkbox)
|
||||
checkbox.set_visible(not visible)
|
||||
checkbox.set_no_show_all(visible)
|
||||
|
||||
def select_application(self, checkbutton):
|
||||
is_active = checkbutton.get_active()
|
||||
|
@ -199,135 +207,84 @@ class TwoFactorWindow(Gtk.Window):
|
|||
else:
|
||||
return True
|
||||
|
||||
def get_checkbox_from_row(self, row):
|
||||
if row:
|
||||
return row.get_children()[0].get_children()[0].get_children()[0]
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def select_row(self, listbox, listbox_row):
|
||||
index = listbox_row.get_index()
|
||||
button_visible = self.remove_button.get_visible()
|
||||
|
||||
if self.checkboxes[index]:
|
||||
if button_visible:
|
||||
clicked = self.checkboxes[index].get_active()
|
||||
self.checkboxes[index].set_active(not clicked)
|
||||
else:
|
||||
if self.selected_app_idx:
|
||||
listrow_box = self.listbox.get_row_at_index(
|
||||
self.selected_app_idx)
|
||||
self.listbox.unselect_row(listbox_row)
|
||||
self.selected_app_idx = index
|
||||
listrow_box = self.listbox.get_row_at_index(index)
|
||||
self.listbox.select_row(listbox_row)
|
||||
checkbox = self.get_checkbox_from_row(listbox_row)
|
||||
if button_visible:
|
||||
checkbox.set_active(not checkbox.get_active())
|
||||
else:
|
||||
if self.selected_app_idx:
|
||||
listrow_box = self.listbox.get_row_at_index(
|
||||
self.selected_app_idx)
|
||||
self.listbox.unselect_row(listbox_row)
|
||||
self.selected_app_idx = index
|
||||
listrow_box = self.listbox.get_row_at_index(index)
|
||||
self.listbox.select_row(listbox_row)
|
||||
|
||||
# TODO : show a nice message when no application is added
|
||||
def generate_applications_list(self):
|
||||
box_outer = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6)
|
||||
list_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6)
|
||||
count = self.app.provider.count_providers()
|
||||
if count > 0:
|
||||
# Create a ScrolledWindow for installed applications
|
||||
scrolled_win = Gtk.ScrolledWindow()
|
||||
scrolled_win.add_with_viewport(box_outer)
|
||||
self.get_children()[0].pack_start(scrolled_win, True, True, 0)
|
||||
# Create a ScrolledWindow for installed applications
|
||||
self.listbox = Gtk.ListBox()
|
||||
self.listbox.get_style_context().add_class("applications-list")
|
||||
self.listbox.set_adjustment()
|
||||
self.listbox.connect("row_activated", self.select_row)
|
||||
self.listbox.set_selection_mode(Gtk.SelectionMode.SINGLE)
|
||||
list_box.pack_start(self.listbox, True, True, 0)
|
||||
|
||||
self.listbox = Gtk.ListBox()
|
||||
self.listbox.get_style_context().add_class("applications-list")
|
||||
self.listbox.set_adjustment()
|
||||
self.listbox.connect("row_activated", self.select_row)
|
||||
self.listbox.set_selection_mode(Gtk.SelectionMode.SINGLE)
|
||||
box_outer.pack_start(self.listbox, True, True, 0)
|
||||
providers = self.app.provider.fetch_providers()
|
||||
i = 0
|
||||
while i < count:
|
||||
row = self.generate_listrow(providers[i][0], providers[i][1],
|
||||
providers[i][2], providers[i][3])
|
||||
self.listbox.add(row)
|
||||
i += 1
|
||||
scrolled_win = Gtk.ScrolledWindow()
|
||||
scrolled_win.add_with_viewport(list_box)
|
||||
self.get_children()[0].get_children()[0].pack_start(scrolled_win, True, True, 0)
|
||||
|
||||
providers = self.app.provider.fetch_providers()
|
||||
i = 0
|
||||
while i < len(providers):
|
||||
row = ListBoxRow(self, providers[i][0], providers[i][1],
|
||||
providers[i][2], providers[i][3])
|
||||
self.listbox.add(row.get_listrow())
|
||||
i += 1
|
||||
|
||||
nolist_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6)
|
||||
vbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
|
||||
|
||||
logo_image = Gtk.Image()
|
||||
logo_image.set_from_icon_name("dialog-information-symbolic",
|
||||
Gtk.IconSize.DIALOG)
|
||||
vbox.pack_start(logo_image, False, False, 6)
|
||||
|
||||
no_proivders_label = Gtk.Label()
|
||||
no_proivders_label.set_text("There's no providers at the moment")
|
||||
vbox.pack_start(no_proivders_label, False, False, 6)
|
||||
|
||||
nolist_box.pack_start(vbox, True, True, 0)
|
||||
self.get_children()[0].pack_start(nolist_box, True, True, 0)
|
||||
if len(providers) == 0:
|
||||
self.get_children()[0].get_children()[0].set_no_show_all(True)
|
||||
self.get_children()[0].get_children()[0].set_visible(False)
|
||||
else:
|
||||
vbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
|
||||
self.get_children()[0].get_children()[1].set_no_show_all(True)
|
||||
self.get_children()[0].get_children()[1].set_visible(False)
|
||||
|
||||
logo_image = Gtk.Image()
|
||||
logo_image.set_from_icon_name("dialog-information-symbolic",
|
||||
Gtk.IconSize.DIALOG)
|
||||
vbox.pack_start(logo_image, False, False, 6)
|
||||
|
||||
no_proivders_label = Gtk.Label()
|
||||
no_proivders_label.set_text("There's no providers at the moment")
|
||||
vbox.pack_start(no_proivders_label, False, False, 6)
|
||||
|
||||
box_outer.pack_start(vbox, True, True, 0)
|
||||
self.get_children()[0].pack_start(box_outer, True, True, 0)
|
||||
|
||||
def update_list(self, id, name, secret_code, image):
|
||||
row = self.generate_listrow(id, name, secret_code, image)
|
||||
self.listbox.add(row)
|
||||
row = ListBoxRow(self, id, name, secret_code, image)
|
||||
self.listbox.add(row.get_listrow())
|
||||
self.listbox.show_all()
|
||||
|
||||
|
||||
def generate_listrow(self, id, name, secret_code, logo):
|
||||
row = Gtk.ListBoxRow()
|
||||
row.get_style_context().add_class("application-list-row")
|
||||
vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
|
||||
hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
|
||||
pass_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
|
||||
pass_box.set_visible(False)
|
||||
vbox.pack_start(hbox, True, True, 6)
|
||||
vbox.pack_start(pass_box, True, True, 6)
|
||||
|
||||
# ID
|
||||
label_id = Gtk.Label()
|
||||
label_id.set_text(str(id))
|
||||
label_id.set_visible(False)
|
||||
label_id.set_no_show_all(True)
|
||||
vbox.pack_end(label_id, False, False, 0)
|
||||
|
||||
# Checkbox
|
||||
checkbox = Gtk.CheckButton()
|
||||
checkbox.set_visible(False)
|
||||
checkbox.set_no_show_all(True)
|
||||
checkbox.connect("toggled", self.select_application)
|
||||
hbox.pack_start(checkbox, False, True, 6)
|
||||
self.checkboxes.append(checkbox)
|
||||
|
||||
# Provider logo
|
||||
provider_logo = self.app.provider.get_provider_image(logo)
|
||||
hbox.pack_start(provider_logo, False, True, 6)
|
||||
|
||||
# Provider name
|
||||
application_name = Gtk.Label(xalign=0)
|
||||
application_name.get_style_context().add_class("application-name")
|
||||
application_name.set_text(name)
|
||||
hbox.pack_start(application_name, True, True, 6)
|
||||
# Copy button
|
||||
copy_event = Gtk.EventBox()
|
||||
copy_button = Gtk.Image(xalign=0)
|
||||
copy_button.set_from_icon_name("edit-copy-symbolic",
|
||||
Gtk.IconSize.SMALL_TOOLBAR)
|
||||
copy_button.set_tooltip_text("Copy the generated code..")
|
||||
copy_event.connect("button-press-event", self.copy_code)
|
||||
copy_event.add(copy_button)
|
||||
hbox.pack_end(copy_event, False, True, 6)
|
||||
|
||||
# Remove button
|
||||
remove_event = Gtk.EventBox()
|
||||
remove_button = Gtk.Image(xalign=0)
|
||||
remove_button.set_from_icon_name("list-remove-symbolic",
|
||||
Gtk.IconSize.SMALL_TOOLBAR)
|
||||
remove_button.set_tooltip_text("Remove the source..")
|
||||
remove_event.add(remove_button)
|
||||
remove_event.connect("button-press-event", self.remove_application)
|
||||
hbox.pack_end(remove_event, False, True, 6)
|
||||
|
||||
code_label = Gtk.Label(xalign=0)
|
||||
code_label.get_style_context().add_class("application-secret-code")
|
||||
# TODO : show the real secret code
|
||||
code_label.set_text(secret_code)
|
||||
pass_box.set_no_show_all(True)
|
||||
|
||||
pass_box.pack_start(code_label, False, True, 0)
|
||||
|
||||
row.add(vbox)
|
||||
return row
|
||||
|
||||
|
||||
def copy_code(self, *args):
|
||||
if len(args) > 0:
|
||||
row = args[0].get_parent().get_parent().get_parent()
|
||||
self.listbox.select_row(row)
|
||||
selected_row = self.listbox.get_selected_row()
|
||||
label = selected_row.get_children()[0].get_children()[1].get_children()
|
||||
code = label[0].get_text()
|
||||
|
@ -338,40 +295,35 @@ class TwoFactorWindow(Gtk.Window):
|
|||
except Exception as e:
|
||||
logging.error(str(e))
|
||||
|
||||
def refresh_window(self, *args):
|
||||
def refresh_window(self, force_refresh=False):
|
||||
mainbox = self.get_children()[0]
|
||||
self.checkboxes = []
|
||||
count = self.app.provider.count_providers()
|
||||
for widget in mainbox:
|
||||
mainbox.remove(widget)
|
||||
self.genereate_searchbar()
|
||||
self.generate_applications_list()
|
||||
if count == 0:
|
||||
self.get_children()[0].get_children()[0].set_no_show_all(True)
|
||||
self.get_children()[0].get_children()[0].set_visible(False)
|
||||
|
||||
else:
|
||||
self.get_children()[0].get_children()[1].set_no_show_all(True)
|
||||
self.get_children()[0].get_children()[1].set_visible(False)
|
||||
headerbar = self.get_children()[1]
|
||||
left_box = headerbar.get_children()[0]
|
||||
right_box = headerbar.get_children()[1]
|
||||
right_box.get_children()[0].set_visible(count > 0)
|
||||
if count == 0:
|
||||
self.listbox.set_selection_mode(Gtk.SelectionMode.SINGLE)
|
||||
if count > 0 and self.listbox:
|
||||
if self.listbox.get_selection_mode() == Gtk.SelectionMode.MULTIPLE:
|
||||
left_box.get_children()[0].set_visible(count > 0)
|
||||
else:
|
||||
left_box.get_children()[0].set_visible(False)
|
||||
self.get_children()[0].show_all()
|
||||
self.listbox.set_selection_mode(Gtk.SelectionMode.SINGLE)
|
||||
left_box.get_children()[0].set_visible(False)
|
||||
|
||||
# TODO : add remove from database
|
||||
def remove_application(self, *args):
|
||||
confirmation = ConfirmationMessage(self, "Are you sure??")
|
||||
|
||||
def remove_provider(self, *args):
|
||||
if len(args) > 0:
|
||||
row = args[0].get_parent().get_parent().get_parent()
|
||||
self.listbox.select_row(row)
|
||||
|
||||
message = "Do you really want to remove the two-factor auth provider?"
|
||||
confirmation = ConfirmationMessage(self, message)
|
||||
confirmation.show()
|
||||
if confirmation.get_confirmation():
|
||||
if self.listbox.get_selected_row():
|
||||
selected_row = self.listbox.get_selected_row()
|
||||
del self.checkboxes[selected_row.get_index()]
|
||||
index = selected_row.get_index() + 1
|
||||
if index > len(self.listbox.get_children()) - 1:
|
||||
index = selected_row.get_index() - 1
|
||||
self.listbox.select_row(self.listbox.get_row_at_index(index))
|
||||
self.listbox.remove(selected_row)
|
||||
label_id = selected_row.get_children()[0].get_children()[2]
|
||||
self.app.provider.remove_from_database(int(label_id.get_text()))
|
Loading…
Add table
Reference in a new issue