Files
YMS/ymsgui.py
T
2026-09-22 10:20:22 +02:00

246 lines
7.9 KiB
Python
Executable File

#!/usr/bin/python3
# SPDX-License-Identifier: WTFPL
"""
YMS native GUI.
Runs as user SYSTEM with id=0
Configuration file located at ~/.config/ymsgui.conf:
[DB]
host = localhost
db = yms
user = ymsgui
pass = topsecret
[Documents]
basepath = /var/local/lib/yms
[Data]
shapedir = /usr/local/share/yms/shapes
"""
import os
import sys
import locale
import gettext
import configparser
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, Gdk, Rsvg
from yms import database
from yms import gtkutils, combobox, treeview
from yms.model import user
from yms.model import vessel
from yms.lookup import Lookup
from yms.view.base import BaseView
from yms.view.storage import StorageView
from yms.view.boxes import BoxesView
from yms.view.companies import CompaniesView
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.cables import CablesView
from yms.view.fuses import FusesView
from yms.view.documents import DocumentsView
from yms.view.status import StatusView
__author__ = "Thomas Hooge"
__copyright__ = "Copyright 2024-2026"
__version__ = "0.2.2"
__email__ = "thomas@hoogi.de"
__status__ = "Development"
class AppContext:
cfg = {
'cfgfile': '~/.config/ymsgui.conf',
'host': 'localhost',
'db': 'yms',
'user': 'yms',
'docpath': '/var/local/lib/yms',
'shapedir': '/usr/local/share/yms/shapes'
}
# mapping between PHP and Python
datefmt_map = {
'Y-m-d': '%Y-%m-%d', # ISO / international / technical
'd.m.Y': '%d.%m.%Y', # Germany / Central & Northern Europe
'd/m/Y': '%d/%m/%Y', # UK / France / Spain / Italy / Australia / NZ
'd-m-Y': '%d-%m-%Y', # Netherlands / Denmark / Belgium
'd M Y': '%d %b %Y', # nautical documents / logbooks / unambiguous
'm.d.Y': '%m.%d.%Y', # USA
'm/d/Y': '%m/%d/%Y', # USA
'Y/m/d': '%Y/%m/%d' # Japan / China
}
def __init__(self):
self.__version__ = __version__
self.db = None
self.user = None
self.vessel = None
self.ui_path = os.path.join(self.get_sharepath(), "yms", "ui")
self.textdomain = "ymsgui"
self.datefmt = '%d.%m.%Y'
# system settings stored in database
self.curr_sym = '€'
self.img_max_width = 1024
self.img_max_height = 768
def set_db(self, db):
self.db = db
self.lookup = Lookup(db)
def load(self):
self.user = user.User(db, 0)
self.vessel = vessel.Vessel(db)
# load date format from db settings
sql = "SELECT valstr FROM settings WHERE userid=0 AND sno=2"
self.db.cur.execute(sql)
row = self.db.cur.fetchone()
if row is not None:
try:
self.datefmt = self.datefmt_map[row[0]]
except KeyError:
# almost impossible case where db value is not in mapping
print("internal error: format mapping missinf")
else:
# perhaps create missing record here?
pass
self.lookup.load()
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.db = context.db
self.modules = {
"base": BaseView(context),
"storage": StorageView(context),
"box": BoxesView(context),
"companies": CompaniesView(context),
"equipment": EquipmentView(context),
"inventory": InventoryView(context),
"provisions": ProvisionsView(context),
"cables": CablesView(context),
"fuses": FusesView(context),
"maintenance": MaintenanceView(context),
"projects": ProjectsView(context),
"documents": DocumentsView(context),
"status": StatusView(context)
}
builder = Gtk.Builder()
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")
self.notebook = builder.get_object("ntb_modules")
for module in self.modules.values():
self.notebook.append_page(module.topwidget, Gtk.Label(label=module.title))
self.db_poll_ts = context.db.get_timestamp()
GLib.timeout_add_seconds(30, self.db_change_poller)
print("Frontend initialized.")
def run(self):
#self.label['vessel'].set_text(self._vessel.vesselname)
self.window.show_all()
Gtk.main()
def db_change_poller(self):
# get all list ids which changes since last query
print("db list change poller")
#sql = "SELECT revid FROM revision WHERE changed>%s"
self.db_poll_ts = self.db.get_timestamp()
return True
def on_ntb_modules_switch_page(self, notebook, page, page_num):
print("Neue Seite:", page_num)
# TODO here re-load lists
def on_destroy(self, widget, data=None):
Gtk.main_quit()
def create_config(cfg, cfgfile):
# create inital config file
with open(cfgfile, 'w') as fh:
fh.write('[DB]\n')
fh.write('host = {}\n'.format(cfg['host']))
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\n'.format(cfg['docpath']))
fh.write('[Data]\n')
fh.write('shapedir = {}\n'.format(cfg['shapedir']))
# secure configuration
os.chmod(cfgfile, 0o600)
if __name__ == "__main__":
print("Startup sequence entered...")
context = AppContext()
locale.setlocale(locale.LC_ALL, '')
gettext.bindtextdomain(context.textdomain, 'locale')
gettext.textdomain(context.textdomain)
gettext.install(context.textdomain)
config = configparser.ConfigParser()
cfgfile = os.path.expanduser(context.cfg['cfgfile'])
if not config.read(cfgfile):
create_config(context.cfg, cfgfile)
gtkutils.messagebox(
_("Configuration file '{}' not found.\n"
"Default configuration has been created.\n"
"Please edit that file according to your needs.\n\n"
"Then restart this program."
).format(cfgfile), title="YMS")
else:
context.cfg['host'] = config.get('DB', 'host')
context.cfg['db'] = config.get('DB', 'db')
db = database.Database(context.cfg['host'], context.cfg['db'])
context.set_db(db)
connected = False
if context.cfg['host'] == 'socket':
connected = db.connect_local()
else:
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:
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()
context.db.disconnect()
else:
errno, errtxt = context.db.last_error()
gtkutils.messagebox(
_("Database connection cannot be established.\n\n"
"Server: {}\nDatabase: {}\n"
"Error: {} ({})").format(context.cfg['host'], context.cfg['db'], errtxt, errno),
title="YMS")
# Another fine product of the sirius cybernetics corporation