Work on native gui: views and database model

This commit is contained in:
2026-09-09 12:20:57 +02:00
parent 66f017567e
commit b21a6beffd
35 changed files with 5649 additions and 3266 deletions
+94 -195
View File
@@ -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