diff --git a/yms/view/base.py b/yms/view/base.py new file mode 100644 index 0000000..3353bcc --- /dev/null +++ b/yms/view/base.py @@ -0,0 +1,199 @@ +# SPDX-License-Identifier: WTFPL + +""" +Spezial: +Notebook mit Vessel, User, Lists, Company + +""" + +import os +import gi +gi.require_version("Gtk", "3.0") +gi.require_version('Rsvg', '2.0') +from gi.repository import GLib, Gtk, Gdk, Rsvg + +from .view import View +from yms import gtkutils, combobox, treeview + +class BaseView(View): + + def __init__(self, context): + super().__init__(context, _("Base Data"), "base.ui") + #self.title = _("Base Data") + #builder = Gtk.Builder() + #builder.set_translation_domain(context.textdomain) + #builder.add_from_file(os.path.join(context.ui_path, "base.ui")) + #builder.connect_signals(self) + #self.topwidget = builder.get_object("topobject") + #gtkutils.init_named_controls(self, builder) + + # get current vessel data + self.db.cur.execute("SELECT valint FROM settings WHERE userid=0 AND sno=1") + row = self.db.cur.fetchone() + if not row: + self.context.vessel.vid = 0 + self.context.vessel.vesselname = "Ghost" + self.db.cur.execute("INSERT INTO settings (userid, sno, valint) VALUES (0, 1, 0)") + self.db.conn.commit() + else: + self.context.vessel.vid = row[0] + + # get list of vessels + vessellist = {} + self.db.dcur.execute("SELECT vid, vesselname, model FROM vessel ORDER BY vid") + for row in self.db.dcur.fetchall(): + vessellist[row['vid']] = row['vesselname'] + " - " + row['model'] + combobox.load_from_dict(self.combo['vessel'], vessellist, self.context.vessel.vid) + # vessel load triggered from combobox init + + # prepare lists + self.lst_user = Gtk.ListStore(int, str) + self.db.cur.execute("SELECT userid, login FROM user WHERE userid > 0 ORDER BY login") + for row in self.db.cur.fetchall(): + print(row) + 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) + + # companies + """ + self.lst_companies = Gtk.ListStore(int, str, str, str) + self.db.cur.execute("SELECT compid, compname, comptype FROM company ORDER BY compname") + for row in self.db.cur.fetchall(): + self.lst_user.append(row) + treeview.add_column(self.tree['companies'], _('ID'), 0) + treeview.add_column(self.tree['companies'], _('Name'), 1) + treeview.add_column(self.tree['companies'], _('Type'), 2) + self.tree['companies'].set_model(self.lst_companies) + """ + + # 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) + + # load shape of vessel if available + if self.context.vessel.vid > 0: + self.db.cur.execute("SELECT shapefile FROM vessel WHERE vid=%s", (self.context.vessel.vid, )) + filename = self.db.cur.fetchone()[0] or '' + shapefilepath = os.path.join(context.cfg['shapedir'], filename) + if os.path.isfile(shapefilepath): + handle = Rsvg.Handle() + self._svg = handle.new_from_file(shapefilepath) + #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) + + # WIP generic remarks handling + self.text['vessel_remarks'].set_size_request(200, 70) + + def on_cmb_vessel_changed(self, widget): + self.context.vessel.load(combobox.get_list_value(widget)) + print(self.context.vessel) + sql = ("UPDATE settings SET valint=%s WHERE userid=0 AND sno=1") + self.db.cur.execute(sql, (self.context.vessel.vid,)) + self.db.conn.commit() + # Show data + self.entry['vesselname'].set_text(self.context.vessel.vesselname) + self.entry['model'].set_text(self.context.vessel.model) + self.text['vessel_remarks'].get_buffer().set_text(self.context.vessel.remarks or '') + self.spin['loa'].set_value(9.80) + + def on_but_vessel_add_clicked(self, widget): + print("Add vessel") + + def on_but_vessel_save_clicked(self, widget): + print("Save vessel") + + def on_but_comp_new_clicked(self, widget): + print("New company") + + def on_but_comp_edit_clicked(self, widget): + print("Edit company") + + def on_but_comp_detail_clicked(self, widget): + print("Company details") + + def on_but_comp_save_clicked(self, widget): + print("Save company") + + def on_but_comp_cancel_clicked(self, widget): + pass + + def user_display(self): + self.entry['user_login'].set_text(self.context.user.login) + self.entry['user_displayname'].set_text(self.context.user.displayname) + combobox.activate_row(self.combo['user_role'], self.context.user.role) + combobox.activate_row(self.combo['user_lang'], self.context.user.language) + self.check['user_locked'].set_active('locked' in self.context.user.flags) + self.check['user_deleted'].set_active('deleted' in self.context.user.flags) + self.entry['user_pass'].set_text('') + + def on_tvw_user_row_activated(self, widget, path, column): + # load selected user + self.context.user.user_id = treeview.get_value(widget, 0) + self.context.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" + self.context.user.create() + self.lst_user.append((self.context.user.user_id, self.context.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.context.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.context.user.user_id = treeview.get_value(self.tree['user']) + self.context.user.load() + self.user_display() + + def on_but_user_newpass_clicked(self, widget): + self.entry['user_pass'].set_text(self.context.user.genpass()) + + 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.context.user.login: + sql = "SELECT COUNT(login) FROM user WHERE login=%s" + self.db.cur.execute(sql, (newlogin, )) + if self.db.cur.fetchone()[0] > 0: + # TODO set error indicator + print("Error: Login already exists!") + return + sql = "UPDATE user SET login=%s WHERE userid=%s" + self.db.cur.execute(sql, (newlogin, self.context.user.user_id)) + treeview.set_value(self.tree['user'], 1, newlogin) + self.context.user.displayname = self.entry['user_displayname'].get_text() + self.context.user.role = combobox.get_list_value(self.combo['user_role']) + self.context.user.language = combobox.get_list_value(self.combo['user_lang']) + self.context.user.flags = [] + if self.check['user_locked'].get_active(): self.context.user.flags.append('locked') + if self.check['user_deleted'].get_active(): self.context.user.flags.append('deleted') + self.context.user.save() + # password field is different. if filled set new password + newpass = self.entry['user_pass'].get_text() + if len(newpass) >= 8: + try: + self.context.user.setpass(newpass) + finally: + newpass = None + self.entry['user_pass'].set_text('') + else: + # Password to short + pass diff --git a/yms/view/documents.py b/yms/view/documents.py new file mode 100644 index 0000000..7964bf9 --- /dev/null +++ b/yms/view/documents.py @@ -0,0 +1,19 @@ +# SPDX-License-Identifier: WTFPL + +import os +import gi +gi.require_version("Gtk", "3.0") +from gi.repository import GLib, Gtk, Gdk + +from yms import gtkutils, combobox, treeview + +class DocumentsView: + + def __init__(self, context): + self.title = _("Documents") + builder = Gtk.Builder() + builder.set_translation_domain(context.textdomain) + builder.add_from_file(os.path.join(context.ui_path, "documents.ui")) + builder.connect_signals(self) + self.topwidget = builder.get_object("topobject") + gtkutils.init_named_controls(self, builder) diff --git a/yms/view/equipment.py b/yms/view/equipment.py new file mode 100644 index 0000000..75130ad --- /dev/null +++ b/yms/view/equipment.py @@ -0,0 +1,43 @@ +# SPDX-License-Identifier: WTFPL + +import os +import gi +gi.require_version("Gtk", "3.0") +from gi.repository import GLib, Gtk, Gdk + +from yms import gtkutils, combobox, treeview + +class EquipmentView: + + def __init__(self, context): + self.title = _("Equipment") + builder = Gtk.Builder() + builder.set_translation_domain(context.textdomain) + builder.add_from_file(os.path.join(context.ui_path, "equipment.ui")) + builder.connect_signals(self) + self.topwidget = builder.get_object("stk_equipment") + gtkutils.init_named_controls(self, builder) + + def on_but_new_clicked(self, widget): + self.topwidget.set_visible_child(self.box['equip_edit']) + + def on_but_detail_clicked(self, widget): + self.topwidget.set_visible_child(self.box['equip_detail']) + + def on_but_edit_clicked(self, widget): + self.topwidget.set_visible_child(self.box['equip_edit']) + + def on_but_edit2_clicked(self, widget): + self.topwidget.set_visible_child(self.box['equip_edit']) + + def on_but_close_clicked(self, widget): + self.topwidget.set_visible_child(self.box['equip_detail']) + + def on_but_save_clicked(self, widget): + pass + + def on_but_delete_clicked(self, widget): + self.topwidget.set_visible_child(self.box['equipment']) + + def on_but_cancel_clicked(self, widget): + self.topwidget.set_visible_child(self.box['equipment']) diff --git a/yms/view/inventory.py b/yms/view/inventory.py new file mode 100644 index 0000000..efbfc3c --- /dev/null +++ b/yms/view/inventory.py @@ -0,0 +1,19 @@ +# SPDX-License-Identifier: WTFPL + +import os +import gi +gi.require_version("Gtk", "3.0") +from gi.repository import GLib, Gtk, Gdk + +from yms import gtkutils, combobox, treeview + +class InventoryView: + + def __init__(self, context): + self.title = _("Inventory") + builder = Gtk.Builder() + builder.set_translation_domain(context.textdomain) + builder.add_from_file(os.path.join(context.ui_path, "inventory.ui")) + builder.connect_signals(self) + self.topwidget = builder.get_object("topobject") + gtkutils.init_named_controls(self, builder) diff --git a/yms/view/maintenance.py b/yms/view/maintenance.py new file mode 100644 index 0000000..ad9dada --- /dev/null +++ b/yms/view/maintenance.py @@ -0,0 +1,19 @@ +# SPDX-License-Identifier: WTFPL + +import os +import gi +gi.require_version("Gtk", "3.0") +from gi.repository import Gtk + +from yms import gtkutils, combobox, treeview + +class MaintenanceView: + + def __init__(self, context): + self.title = _("Maintenance") + builder = Gtk.Builder() + builder.set_translation_domain(context.textdomain) + builder.add_from_file(os.path.join(context.ui_path, "maintenance.ui")) + builder.connect_signals(self) + self.topwidget = builder.get_object("topobject") + gtkutils.init_named_controls(self, builder) diff --git a/yms/view/projects.py b/yms/view/projects.py new file mode 100644 index 0000000..6ea755f --- /dev/null +++ b/yms/view/projects.py @@ -0,0 +1,19 @@ +# SPDX-License-Identifier: WTFPL + +import os +import gi +gi.require_version("Gtk", "3.0") +from gi.repository import Gtk + +from yms import gtkutils, combobox, treeview + +class ProjectsView: + + def __init__(self, context): + self.title = _("Projects") + builder = Gtk.Builder() + builder.set_translation_domain(context.textdomain) + builder.add_from_file(os.path.join(context.ui_path, "projects.ui")) + builder.connect_signals(self) + self.topwidget = builder.get_object("topobject") + gtkutils.init_named_controls(self, builder) diff --git a/yms/view/provisions.py b/yms/view/provisions.py new file mode 100644 index 0000000..3a721e4 --- /dev/null +++ b/yms/view/provisions.py @@ -0,0 +1,19 @@ +# SPDX-License-Identifier: WTFPL + +import os +import gi +gi.require_version("Gtk", "3.0") +from gi.repository import Gtk + +from yms import gtkutils, combobox, treeview + +class ProvisionsView: + + def __init__(self, context): + self.title = _("Provisions") + builder = Gtk.Builder() + builder.set_translation_domain(context.textdomain) + builder.add_from_file(os.path.join(context.ui_path, "provisions.ui")) + builder.connect_signals(self) + self.topwidget = builder.get_object("topobject") + gtkutils.init_named_controls(self, builder) diff --git a/yms/view/status.py b/yms/view/status.py new file mode 100644 index 0000000..e9041c8 --- /dev/null +++ b/yms/view/status.py @@ -0,0 +1,44 @@ +# SPDX-License-Identifier: WTFPL + +import os +import gi +gi.require_version("Gtk", "3.0") +from gi.repository import Gtk + +from yms import gtkutils, combobox, treeview + +class StatusView: + + def __init__(self, context): + self.context = context + self.title = _("Status") + builder = Gtk.Builder() + builder.set_translation_domain(context.textdomain) + builder.add_from_file(os.path.join(context.ui_path, "status.ui")) + builder.connect_signals(self) + self.topwidget = builder.get_object("topobject") + gtkutils.init_named_controls(self, builder) + + # Load table status info + self.lst_tables = Gtk.ListStore(str, int) + for row in self.get_table_counts(): + self.lst_tables.append(row) + treeview.add_column(self.tree['tables'], 'Table', 0) + treeview.add_column(self.tree['tables'], '# Records', 1) + self.tree['tables'].set_model(self.lst_tables) + + def get_table_counts(self): + # Detect all tables in database + sql= ("SELECT table_name " + "FROM information_schema.tables " + "WHERE table_schema='yms' AND table_type='BASE TABLE' " + "ORDER BY table_name") + self.context.db.cur.execute(sql) + tables = [row[0] for row in self.context.db.cur.fetchall()] + # Loop over tables and get current record count + records = [] + for table in tables: + sql = f"SELECT COUNT(*) FROM {table}" + self.context.db.cur.execute(sql) + records.append((table, self.context.db.cur.fetchone()[0])) + return records diff --git a/yms/view/view.py b/yms/view/view.py new file mode 100644 index 0000000..0b5637e --- /dev/null +++ b/yms/view/view.py @@ -0,0 +1,85 @@ +# SPDX-License-Identifier: WTFPL + +""" +Generic view object + +SPDX-License-Identifier: WTFPL +""" +import os +import gi +gi.require_version("Gtk", "3.0") +from gi.repository import Gtk +from yms import gtkutils + +class View: + def __init__(self, context, title, ui_file): + self.context = context + self.db = context.db + self.vessel = context.vessel + self.user = context.user + self.title = title + builder = Gtk.Builder() + builder.set_translation_domain(context.textdomain) + builder.add_from_file(os.path.join(context.ui_path, ui_file)) + builder.connect_signals(self) + self.topwidget = builder.get_object("topobject") + gtkutils.init_named_controls(self, builder) + + def on_textview_keypress(self, widget, event): + # Generic limit length function + if event.state & Gdk.ModifierType.CONTROL_MASK: + allowed_ctrl_keys = ( + Gdk.KEY_a, + Gdk.KEY_c, + Gdk.KEY_x, + ) + if event.keyval in allowed_ctrl_keys: + return False + allowed_keys = ( + Gdk.KEY_BackSpace, + Gdk.KEY_Delete, + Gdk.KEY_Left, + Gdk.KEY_Right, + Gdk.KEY_Up, + Gdk.KEY_Down, + Gdk.KEY_Home, + Gdk.KEY_End, + Gdk.KEY_Page_Up, + Gdk.KEY_Page_Down, + ) + print("keypress") + if event.keyval in allowed_keys: + return False + buf = widget.get_buffer() + start = buf.get_start_iter() + end = buf.get_end_iter() + text = buf.get_text(start, end, True) + if len(text) >= 150: + return True + return False + + def on_textview_paste(self, widget): + # Generic limit length function + print("paste") + buf = widget.get_buffer() + start = buf.get_start_iter() + end = buf.get_end_iter() + text = buf.get_text(start, end, True) + if buf.get_has_selection(): + sel_start, sel_end = buf.get_selection_bounds() + selected_text = buf.get_text(sel_start, sel_end, True) + current_length = len(text) - len(selected_text) + else: + current_length = len(text) + remaining = 150 - current_length + if remaining <= 0: + return True + clipboard = widget.get_clipboard(Gdk.SELECTION_CLIPBOARD) + cliptext = clipboard.wait_for_text() + if cliptext: + cliptext = cliptext[:remaining] + if buf.get_has_selection(): + sel_start, sel_end = buf.get_selection_bounds() + buf.delete(sel_start, sel_end) + buf.insert_at_cursor(text) + return True