Work on native gui: views and database model
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
#!/usr/bin/python3
|
||||
# SPDX-License-Identifier: WTFPL
|
||||
|
||||
"""
|
||||
|
||||
@@ -15,6 +16,9 @@ Configuration file located at ~/.config/ymsgui.conf:
|
||||
[Documents]
|
||||
basepath = /var/local/yms
|
||||
|
||||
[Data]
|
||||
shapedir = /usr/local/share/yms/shapes
|
||||
|
||||
"""
|
||||
|
||||
import os
|
||||
@@ -27,204 +31,88 @@ os.environ["NO_AT_BRIDGE"] = "1" # fix dbus warnings
|
||||
import gi
|
||||
gi.require_version("Gtk", "3.0")
|
||||
gi.require_version('Rsvg', '2.0')
|
||||
from gi.repository import GLib, Gtk, Rsvg
|
||||
from gi.repository import GLib, Gtk, Gdk, Rsvg
|
||||
|
||||
from yms import database
|
||||
from yms import gtkutils, combobox, treeview
|
||||
from yms import user
|
||||
from yms.model import user
|
||||
from yms.model import vessel
|
||||
|
||||
cfg = {
|
||||
'cfgfile': '~/.config/ymsgui.conf',
|
||||
'host': 'localhost',
|
||||
'db': 'yms',
|
||||
'user': 'yms',
|
||||
'docpath': '/var/local/yms',
|
||||
'shapedir': '/usr/local/bin/yms/shapes'
|
||||
}
|
||||
from yms.view.base import BaseView
|
||||
from yms.view.equipment import EquipmentView
|
||||
from yms.view.inventory import InventoryView
|
||||
from yms.view.provisions import ProvisionsView
|
||||
from yms.view.maintenance import MaintenanceView
|
||||
from yms.view.projects import ProjectsView
|
||||
from yms.view.documents import DocumentsView
|
||||
from yms.view.status import StatusView
|
||||
|
||||
def get_uiprefix():
|
||||
path = os.path.realpath(__file__)
|
||||
if path.startswith("/usr/local/"):
|
||||
return "/usr/local/share"
|
||||
if path.startswith("/usr/"):
|
||||
return "/usr/share"
|
||||
return ""
|
||||
class AppContext:
|
||||
|
||||
class Frontend():
|
||||
cfg = {
|
||||
'cfgfile': '~/.config/ymsgui.conf',
|
||||
'host': 'localhost',
|
||||
'db': 'yms',
|
||||
'user': 'yms',
|
||||
'docpath': '/var/local/yms',
|
||||
'shapedir': '/usr/local/share/yms/shapes'
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
self.db = None
|
||||
self.user = None
|
||||
self.vessel = None
|
||||
self.ui_path = os.path.join(self.get_sharepath(), "yms", "ui")
|
||||
self.textdomain = "ymsgui"
|
||||
|
||||
def set_db(self, db):
|
||||
self.db = db
|
||||
|
||||
def load(self):
|
||||
self.user = user.User(db, 0)
|
||||
self.vessel = vessel.Vessel(db)
|
||||
|
||||
def get_sharepath(self):
|
||||
path = os.path.realpath(__file__)
|
||||
if path.startswith("/usr/local/"):
|
||||
return "/usr/local/share"
|
||||
if path.startswith("/usr/"):
|
||||
return "/usr/share"
|
||||
return ""
|
||||
|
||||
class Frontend:
|
||||
|
||||
def __init__(self, context):
|
||||
self.modules = {
|
||||
"base": BaseView(context),
|
||||
"equipment": EquipmentView(context),
|
||||
"inventory": InventoryView(context),
|
||||
"provisions": ProvisionsView(context),
|
||||
"maintenance": MaintenanceView(context),
|
||||
"projects": ProjectsView(context),
|
||||
"documents": DocumentsView(context),
|
||||
"status": StatusView(context)
|
||||
}
|
||||
builder = Gtk.Builder()
|
||||
builder.add_from_file(os.path.join(get_uiprefix(), "yms", "ui", "main.ui"))
|
||||
builder.set_translation_domain(context.textdomain)
|
||||
ui_path = os.path.join(context.ui_path)
|
||||
builder.add_from_file(os.path.join(ui_path, "main.ui"))
|
||||
builder.connect_signals(self)
|
||||
self.window = builder.get_object("wnd_main")
|
||||
gtkutils.init_named_controls(self, builder)
|
||||
|
||||
# get current vessel id
|
||||
db.cur.execute("SELECT valint FROM settings WHERE userid=0 AND sno=1")
|
||||
row = db.cur.fetchone()
|
||||
if not row:
|
||||
self.vid = 0
|
||||
self.vname = "Ghost"
|
||||
db.cur.execute("INSERT INTO settings (userid, sno, valint) VALUES (0, 1, 0)")
|
||||
db.conn.commit()
|
||||
else:
|
||||
self.vid = row[0]
|
||||
# get vessel infos
|
||||
if self.vid > 0:
|
||||
db.cur.execute("SELECT vesselname, model FROM vessel WHERE vid=%s", (self.vid, ))
|
||||
self.vessel = db.cur.fetchone()
|
||||
print(self.vessel['vesselname'])
|
||||
else:
|
||||
self.vname = "Ghost(0)"
|
||||
|
||||
# get list of vessels
|
||||
vessel = {}
|
||||
db.dcur.execute("SELECT vid, vesselname, model FROM vessel ORDER BY vid")
|
||||
for row in db.dcur.fetchall():
|
||||
vessel[row['vid']] = row['vesselname'] + " - " + row['model']
|
||||
combobox.load_from_dict(self.combo['vessel'], vessel, self.vid)
|
||||
|
||||
# load shape of vessel if available
|
||||
if self.vid > 0:
|
||||
db.cur.execute("SELECT shapefile FROM vessel WHERE vid=%s", (self.vid, ))
|
||||
filename = db.cur.fetchone()[0]
|
||||
handle = Rsvg.Handle()
|
||||
self._svg = handle.new_from_file(os.path.join(cfg['shapedir'], filename))
|
||||
#self._svg = handle.new_from_file(os.path.join(sys.path[0], cfg['shapedir'], "elizabethan29.svg"))
|
||||
#viewport = Rsvg.Rectangle()
|
||||
#viewport.x = 0
|
||||
#viewport.y = 0
|
||||
#viewport.width = 320
|
||||
#viewport.height = 240
|
||||
#self._svg.render_document(ctx, viewport)
|
||||
|
||||
# combos
|
||||
roles = {'captain': 'Captain', 'helmsman': 'Helmsman', 'sailor': 'Sailor'}
|
||||
combobox.load_from_dict(self.combo['user_role'], roles)
|
||||
langs = {'de': 'Deutsch', 'en': 'English'}
|
||||
combobox.load_from_dict(self.combo['user_lang'], langs)
|
||||
|
||||
# prepare lists
|
||||
self.lst_user = Gtk.ListStore(int, str)
|
||||
db.cur.execute("SELECT userid, login FROM user WHERE userid > 0 ORDER BY login")
|
||||
for row in db.cur.fetchall():
|
||||
self.lst_user.append(row)
|
||||
treeview.add_column(self.tree['user'], 'ID', 0)
|
||||
treeview.add_column(self.tree['user'], 'Login', 1)
|
||||
self.tree['user'].set_model(self.lst_user)
|
||||
|
||||
# current selected objects
|
||||
self._user = user.User(db.conn, 0)
|
||||
print(self._user)
|
||||
self.notebook = builder.get_object("ntb_modules")
|
||||
for module in self.modules.values():
|
||||
self.notebook.append_page(module.topwidget, Gtk.Label(label=module.title))
|
||||
print("Frontend initialized.")
|
||||
|
||||
def run(self):
|
||||
self.label['vessel'].set_text(self.vname)
|
||||
#self.label['vessel'].set_text(self._vessel.vesselname)
|
||||
self.window.show_all()
|
||||
Gtk.main()
|
||||
|
||||
def user_display(self):
|
||||
self.entry['user_login'].set_text(self._user.login)
|
||||
self.entry['user_displayname'].set_text(self._user.displayname)
|
||||
combobox.activate_row(self.combo['user_role'], self._user.role)
|
||||
combobox.activate_row(self.combo['user_lang'], self._user.language)
|
||||
self.check['user_locked'].set_active('locked' in self._user.flags)
|
||||
self.check['user_deleted'].set_active('deleted' in self._user.flags)
|
||||
self.entry['user_pass'].set_text('')
|
||||
|
||||
def on_tvw_user_row_activated(self, widget, path, column):
|
||||
# load selected user
|
||||
self._user.user_id = treeview.get_value(widget, 0)
|
||||
self._user.load()
|
||||
self.user_display()
|
||||
|
||||
def on_but_user_new_clicked(self, widget):
|
||||
# get first free user id and create a new record with name "user<nnn>"
|
||||
self._user.create()
|
||||
self.lst_user.append((self._user.user_id, self._user.login))
|
||||
|
||||
def on_but_user_del_clicked(self, widget):
|
||||
print("Delete user")
|
||||
answer = gtkutils.questionbox(
|
||||
_("Delete user!\n"
|
||||
"Are you sure?"
|
||||
), title="YMS")
|
||||
if answer == False:
|
||||
return
|
||||
self._user.delete()
|
||||
selection = self.tree['user'].get_selection()
|
||||
model, tree_iter = selection.get_selected()
|
||||
model.remove(tree_iter)
|
||||
# TODO Select some other user from new list selection
|
||||
self._user.user_id = treeview.get_value(self.tree['user'])
|
||||
self._user.load()
|
||||
self.user_display()
|
||||
|
||||
def on_but_user_newpass_clicked(self, widget):
|
||||
self.entry['user_pass'].set_text(user.User.genpass())
|
||||
# self.lst_user.
|
||||
|
||||
def on_but_user_save_clicked(self, widget):
|
||||
newlogin = self.entry['user_login'].get_text()
|
||||
# check if changed login exists and throw error in that case
|
||||
if newlogin != self._user.login:
|
||||
sql = "SELECT COUNT(login) FROM user WHERE login=%s"
|
||||
db.cur.execute(sql, (newlogin, ))
|
||||
if db.cur.fetchone()[0] > 0:
|
||||
# TODO set error indicator
|
||||
print("Error: Login already exists!")
|
||||
return
|
||||
sql = "UPDATE user SET login=%s WHERE userid=%s"
|
||||
db.cur.execute(sql, (newlogin, self._user.user_id))
|
||||
treeview.set_value(self.tree['user'], 1, newlogin)
|
||||
self._user.displayname = self.entry['user_displayname'].get_text()
|
||||
self._user.role = combobox.get_list_value(self.combo['user_role'])
|
||||
self._user.language = combobox.get_list_value(self.combo['user_lang'])
|
||||
self._user.flags = []
|
||||
if self.check['user_locked'].get_active(): self._user.flags.append('locked')
|
||||
if self.check['user_deleted'].get_active(): self._user.flags.append('deleted')
|
||||
self._user.save()
|
||||
# password field is different. if filled set new password
|
||||
newpass = self.entry['user_pass'].get_text()
|
||||
if len(newpass) >= 8:
|
||||
try:
|
||||
self._user.setpass(newpass)
|
||||
finally:
|
||||
newpass = None
|
||||
self.entry['user_pass'].set_text('')
|
||||
else:
|
||||
# Password to short
|
||||
pass
|
||||
|
||||
def on_but_info_clicked(self, widget):
|
||||
# Userlist
|
||||
sql = ("SELECT userid, login, displayname FROM user ORDER BY login")
|
||||
db.cur.execute(sql)
|
||||
for row in db.cur.fetchall():
|
||||
print(row)
|
||||
|
||||
def on_stk_companies_notify(self, stack, param):
|
||||
print("Stack notify")
|
||||
print(stack, param)
|
||||
|
||||
def on_ntb_modules_switch_page(self, notebook, page, page_num):
|
||||
print("Neue Seite:", page_num)
|
||||
# TODO here re-load lists
|
||||
|
||||
def on_but_equip_detail_clicked(self, widget):
|
||||
self.stack['equipment'].set_visible_child(self.box['equip_detail'])
|
||||
|
||||
def on_but_equip_edit_clicked(self, widget):
|
||||
self.stack['equipment'].set_visible_child(self.box['equip_edit'])
|
||||
|
||||
def on_but_equip_save_clicked(self, widget):
|
||||
pass
|
||||
|
||||
def on_but_equip_cancel_clicked(self, widget):
|
||||
self.stack['equipment'].set_visible_child(self.box['equipment'])
|
||||
|
||||
def on_but_equip_cancel_clicked(self, widget):
|
||||
self.stack['equipment'].set_visible_child(self.box['equipment'])
|
||||
|
||||
def on_destroy(self, widget, data=None):
|
||||
Gtk.main_quit()
|
||||
|
||||
@@ -236,18 +124,24 @@ def create_config(cfgfile):
|
||||
fh.write('db = {}\n'.format(cfg['db']))
|
||||
fh.write('user = {}\n'.format(cfg['user']))
|
||||
fh.write('pass = changeme!\n\n')
|
||||
fh.write('[documents]\n')
|
||||
fh.write('basepath = {}\n'.format(cfg['docpath']))
|
||||
fh.write('[Documents]\n')
|
||||
fh.write('basepath = {}\n\n'.format(cfg['docpath']))
|
||||
fh.write('[Data]\n')
|
||||
fh.write('shapedir = {}\n'.format(cfg['shapedir']))
|
||||
# secure configuration
|
||||
os.chmod(cfgfile, 0o600)
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
context = AppContext()
|
||||
|
||||
locale.setlocale(locale.LC_ALL, '')
|
||||
gettext.bindtextdomain('ymsgui', 'locale')
|
||||
gettext.textdomain('ymsgui')
|
||||
_ = gettext.gettext
|
||||
gettext.bindtextdomain(context.textdomain, 'locale')
|
||||
gettext.textdomain(context.textdomain)
|
||||
gettext.install(context.textdomain)
|
||||
|
||||
config = configparser.ConfigParser()
|
||||
cfgfile = os.path.expanduser(cfg['cfgfile'])
|
||||
cfgfile = os.path.expanduser(context.cfg['cfgfile'])
|
||||
if not config.read(cfgfile):
|
||||
create_config(cfgfile)
|
||||
gtkutils.messagebox(
|
||||
@@ -257,26 +151,31 @@ if __name__ == "__main__":
|
||||
"Then restart this program."
|
||||
).format(cfgfile), title="YMS")
|
||||
else:
|
||||
cfg['host'] = config.get('DB', 'host')
|
||||
cfg['db'] = config.get('DB', 'db')
|
||||
db = database.Database(cfg['host'], cfg['db'])
|
||||
context.cfg['host'] = config.get('DB', 'host')
|
||||
context.cfg['db'] = config.get('DB', 'db')
|
||||
db = database.Database(context.cfg['host'], context.cfg['db'])
|
||||
connected = False
|
||||
if cfg['host'] == 'socket':
|
||||
if context.cfg['host'] == 'socket':
|
||||
connected = db.connect_local()
|
||||
else:
|
||||
cfg['user'] = config.get('DB', 'user')
|
||||
cfg['pass'] = config.get('DB', 'pass')
|
||||
connected = db.connect(cfg['user'], cfg['pass'])
|
||||
context.cfg['user'] = config.get('DB', 'user')
|
||||
context.cfg['pass'] = config.get('DB', 'pass')
|
||||
connected = db.connect(context.cfg['user'], context.cfg['pass'])
|
||||
if connected:
|
||||
app = Frontend()
|
||||
context.set_db(db)
|
||||
context.load()
|
||||
# check if shapedir exists
|
||||
context.cfg['shapedir'] = config.get('Data', 'shapedir')
|
||||
# everything seems to be fine: continue to start frontend
|
||||
app = Frontend(context)
|
||||
app.run()
|
||||
db.disconnect()
|
||||
context.db.disconnect()
|
||||
else:
|
||||
errno, errtxt = db.last_error()
|
||||
errno, errtxt = context.db.last_error()
|
||||
gtkutils.messagebox(
|
||||
_("Database connection cannot be established.\n\n"
|
||||
"Server: {}\nDatabase: {}\n"
|
||||
"Error: {} ({})").format(cfg['host'], cfg['db'], errtxt, errno),
|
||||
"Error: {} ({})").format(context.cfg['host'], context.cfg['db'], errtxt, errno),
|
||||
title="YMS")
|
||||
|
||||
# Another fine product of the sirius cybernetics corporation
|
||||
|
||||
Reference in New Issue
Block a user