Files
KPK/mapa_stolu.py
T
2026-06-23 15:20:56 +02:00

731 lines
26 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from kivy.metrics import dp
from kivy.graphics import Color, RoundedRectangle
from kivy.uix.widget import Widget
from kivy.uix.label import Label
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.scrollview import ScrollView
from kivy.metrics import dp, Metrics
from kivy.uix.button import Button
from kivy.uix.screenmanager import Screen
from kivy.logger import Logger
from ui_utils import ucet_ui_type, UCET_COLORS
from kivy.uix.textinput import TextInput
from kivy.uix.popup import Popup
from ui_utils import _popup_info
import time
import data
MAP_H = 900
MAP_W = 1400
# =====================================================
# TABLE WIDGET (jede nad data.Table)
# =====================================================
class TableWidget(Widget):
def __init__(self, table: data.Table, state=None, on_press=None, **kwargs):
super().__init__(**kwargs)
self.table = table
self.state = state
self.on_press = on_press
self.edit_mode = False
self.dragging = False
self._last_touch_time = 0
with self.canvas.before:
self.color = Color(1, 1, 1, 1)
self.rect = RoundedRectangle(
pos=self.pos,
size=self.size,
radius=[(0, 0)] * 4
)
self.label = Label(
text="",
color=(1, 1, 1, 1),
halign="center",
valign="middle",
font_size=dp(16),
)
self.add_widget(self.label)
self.bind(pos=self._update, size=self._update)
self._update_color()
self._update_text()
# ---------------- COLOR ----------------
def _update_color(self):
u = self.state
if not u:
self.color.rgba = UCET_COLORS["normal"]
return
ui_type = ucet_ui_type(u)
self.color.rgba = UCET_COLORS.get(ui_type, UCET_COLORS["open"])
# ---------------- TEXT ----------------
def _update_text(self):
u = self.state
txt = f"{self.table.name}\n[{self.table.id}]"
if u:
if u.blocked_by:
txt += "\nBLOK"
elif not u.closed_at:
txt += "\nOBSAZENO"
self.label.text = txt
# ---------------- DRAW ----------------
def _update(self, *args):
self.rect.pos = self.pos
self.rect.size = self.size
r = min(self.width, self.height) * self.table.radius
self.rect.radius = [r, r, r, r]
self.label.pos = self.pos
self.label.size = self.size
self.label.text_size = self.size
# ---------------- CLICK ----------------
def on_touch_down(self, touch):
if not self.collide_point(*touch.pos):
return super().on_touch_down(touch)
# ===== POKLADNA =====
if not self.edit_mode:
if self.on_press:
self.on_press(self.table.id)
return True
# ===== EDITOR =====
now = time.time()
# double tap → edit
if now - self._last_touch_time < 0.3:
self.dragging = False
self.open_edit_popup()
return True
self._last_touch_time = now
self.dragging = True
return True
def on_touch_move(self, touch):
if self.edit_mode and self.dragging:
parent = self.parent
if not parent:
return True
new_x = touch.x - self.width / 2
new_y = touch.y - self.height / 2
# clamp
new_x = max(0, min(new_x, parent.width - self.width))
new_y = max(0, min(new_y, parent.height - self.height))
self.pos = (new_x, new_y)
self.table.pos_x = int(new_x / Metrics.density)
self.table.pos_y = int(new_y / Metrics.density)
return True
return super().on_touch_move(touch)
def on_touch_up(self, touch):
if self.dragging:
grid = dp(20)
self.table.pos_x = int((self.table.pos_x // grid) * grid)
self.table.pos_y = int((self.table.pos_y // grid) * grid)
self.dragging = False
return super().on_touch_up(touch)
def _check_id_duplicate(self, instance, value):
new_id = value.strip()
if not new_id:
instance.background_color = (1, 1, 1, 1)
return
old_id = str(self.table.id).strip()
duplicate = False
rooms_with_id = []
try:
provider = self.parent.parent.parent.provider
rooms = provider.get_rooms() if provider else []
except Exception:
rooms = []
for room in rooms:
room_name = getattr(room, "room_name", "Room")
for t in getattr(room, "stoly", []):
if str(t.id).strip() == new_id and new_id != old_id:
duplicate = True
rooms_with_id.append(room_name)
break
if duplicate:
instance.background_color = (1, 0.3, 0.3, 1)
_popup_info("Duplicitní ID",f"ID existuje v: {', '.join(rooms_with_id)}")
else:
instance.background_color = (1, 1, 1, 1)
def open_edit_popup(self):
from kivy.uix.scrollview import ScrollView
# ================= ROOT =================
root = BoxLayout(orientation="vertical", spacing=dp(5), padding=dp(5))
scroll = ScrollView(size_hint=(1, 1))
content = BoxLayout(
orientation="vertical",
spacing=dp(5),
size_hint_y=None
)
content.bind(minimum_height=content.setter("height"))
scroll.add_widget(content)
root.add_widget(scroll)
# ================= INPUTY =================
id_in = TextInput(text=str(self.table.id), multiline=False)
id_in.bind(text=self._check_id_duplicate)
name_in = TextInput(text=self.table.name, multiline=False)
x_in = TextInput(text=str(self.table.pos_x), multiline=False)
y_in = TextInput(text=str(self.table.pos_y), multiline=False)
w_in = TextInput(text=str(self.table.width), multiline=False)
h_in = TextInput(text=str(self.table.height), multiline=False)
r_in = TextInput(text=str(self.table.radius), multiline=False)
def row(label, widget):
r = BoxLayout(size_hint_y=None, height=dp(50))
r.add_widget(Label(text=label, size_hint_x=0.4))
r.add_widget(widget)
return r
content.add_widget(row("ID", id_in))
content.add_widget(row("Název", name_in))
content.add_widget(row("X", x_in))
content.add_widget(row("Y", y_in))
content.add_widget(row("Šířka", w_in))
content.add_widget(row("Výška", h_in))
content.add_widget(row("Radius", r_in))
# ================= POPUP =================
popup = Popup(
title="Edit stolu",
content=root,
size_hint=(0.6, 0.85),
auto_dismiss=False,
)
# ================= HELPERS =================
def _num(txt, default=0):
txt = (txt or "").strip().replace(",", ".")
if not txt:
return default
return float(txt)
# ================= SAVE =================
def save(_):
try:
self.table.id = id_in.text.strip()
self.table.name = name_in.text.strip()
self.table.pos_x = int(_num(x_in.text))
self.table.pos_y = int(_num(y_in.text))
self.table.width = int(_num(w_in.text))
self.table.height = int(_num(h_in.text))
self.table.radius = _num(r_in.text)
self.pos = (
dp(self.table.pos_x),
dp(self.table.pos_y),
)
self.size = (
dp(self.table.width),
dp(self.table.height),
)
self.dragging = False
self._update_text()
self._update()
popup.dismiss()
except Exception as e:
print("Chyba při ukládání:", e)
# ================= DELETE =================
def delete_table(_):
confirm_layout = BoxLayout(orientation="vertical", spacing=10, padding=10)
confirm_layout.add_widget(Label(
text=f"Opravdu smazat stůl?\n\n{self.table.name}",
halign="center"
))
btn_yes = Button(text="ANO smazat")
btn_no = Button(text="Ne")
confirm_layout.add_widget(btn_yes)
confirm_layout.add_widget(btn_no)
confirm_popup = Popup(
title="Smazání stolu",
content=confirm_layout,
size_hint=(0.5, 0.4),
auto_dismiss=False,
)
def do_delete(_):
if self.parent:
self.parent.remove_widget(self)
confirm_popup.dismiss()
popup.dismiss()
btn_yes.bind(on_press=do_delete)
btn_no.bind(on_press=lambda *_: confirm_popup.dismiss())
confirm_popup.open()
# ================= BUTTONY =================
btn_save = Button(text="Uložit", size_hint_y=None, height=dp(50))
btn_cancel = Button(
text="Zrušit",
size_hint_y=None,
height=dp(50),
background_color=(0.6, 0.6, 0.6, 1),
)
btn_delete = Button(
text="Smazat stůl",
size_hint_y=None,
height=dp(50),
background_color=(0.8, 0.2, 0.2, 1),
)
btn_row = BoxLayout(
orientation="horizontal",
spacing=dp(5),
size_hint_y=None,
height=dp(50),
)
btn_row.add_widget(btn_save)
btn_row.add_widget(btn_cancel)
btn_row.add_widget(btn_delete)
root.add_widget(btn_row)
# ================= BINDY =================
btn_save.bind(on_press=save)
btn_delete.bind(on_press=delete_table)
btn_cancel.bind(on_press=lambda *_: popup.dismiss())
popup.bind(on_dismiss=lambda *_: setattr(self, "dragging", False))
popup.open()
# =====================================================
# PROVIDER (vrací data.Room přímo)
# =====================================================
class RoomMapProvider:
def __init__(self, controller=None):
self.controller = controller
self.current_room = None
def set_current_room(self, room_name):
self.current_room = room_name
def get_table_states(self):
if not self.controller:
return {}
ucty = self.controller.load_stoly(closed=False) or []
return {str(u.stul): u for u in ucty}
def get_rooms(self):
if not self.controller or not self.controller.mapa_stolu:
return []
rooms_out = []
for r in self.controller.mapa_stolu.rooms:
# ---------------- ROOM ----------------
if isinstance(r, dict):
room_name = r.get("room_name")
raw_tables = r.get("stoly", [])
else:
room_name = getattr(r, "room_name", None)
raw_tables = getattr(r, "stoly", [])
tables = []
# ---------------- TABLES ----------------
for t in raw_tables:
if isinstance(t, dict):
tables.append(data.Table(
id=t.get("id"),
name=t.get("name"),
pos_x=t.get("pos_x", 0),
pos_y=t.get("pos_y", 0),
width=t.get("width", 100),
height=t.get("height", 100),
radius=t.get("radius", 0.0),
))
else:
# už je to data.Table → použij rovnou
tables.append(t)
rooms_out.append(data.Room(
room_name=room_name,
stoly=tables
))
limit_room = self._limit_room()
if limit_room:
rooms_out.append(limit_room)
return rooms_out
def _limit_room(self):
if not self.controller:
return None
if hasattr(self.controller, "limits_room_enabled") and not self.controller.limits_room_enabled():
return None
active = str(self.current_room or "").strip().lower() == "limity"
if active and hasattr(self.controller, "load_limit_tables"):
limits = self.controller.load_limit_tables(force=True) or []
elif hasattr(self.controller, "cached_limit_tables"):
limits = self.controller.cached_limit_tables() or []
else:
limits = []
if active:
Logger.info(f"RoomMapProvider: refreshing Limity room with {len(limits)} tables")
cols = 4
w = 260
h = 110
sp_x = 24
sp_y = 22
start_x = 30
start_y = 760
tables = []
for idx, item in enumerate(limits):
col = idx % cols
row = idx // cols
tables.append(data.Table(
id=item.table_id,
name=item.name or item.menolimit or item.table_id,
pos_x=start_x + col * (w + sp_x),
pos_y=max(20, start_y - row * (h + sp_y)),
width=w,
height=h,
radius=0.05,
))
return data.Room(room_name="Limity", stoly=tables)
def _fallback_rooms(self):
return [
data.Room(
room_name="Test, mapa neexistuje",
stoly=[
data.Table(
id="1",
name="VIP",
pos_x=150,
pos_y=500,
width=100,
height=100,
radius=1.0,
),
data.Table(
id="2",
name="Bar",
pos_x=380,
pos_y=420,
width=100,
height=100,
radius=0.0,
),
],
)
]
# =====================================================
# MAP WIDGET
# =====================================================
class RoomTableMapWidget(BoxLayout):
def __init__(self, *, provider, on_select, **kwargs):
super().__init__(orientation="vertical", **kwargs)
self.provider = provider
self.on_select = on_select
self.current_room = None
self.scroll = ScrollView(
size_hint=(1, 1),
do_scroll_x=True,
do_scroll_y=True,
bar_width=dp(10),
)
self.map_area = FloatLayout(
size_hint=(None, None),
size=(dp(MAP_W), dp(MAP_H)),
)
self.scroll.add_widget(self.map_area)
self.add_widget(self.scroll)
# ---------------- REFRESH ----------------
def refresh(self):
self.map_area.clear_widgets()
if hasattr(self.provider, "set_current_room"):
self.provider.set_current_room(self.current_room)
rooms = self.provider.get_rooms()
states = self.provider.get_table_states() if hasattr(self.provider, "get_table_states") else {}
if not self.current_room and rooms:
self.current_room = self._get_room_name(rooms[0])
room = next((r for r in rooms if self._get_room_name(r) == self.current_room), None)
if not room:
return
for t in room.stoly:
u = None if getattr(self, "edit_mode", False) else states.get(str(t.id))
widget = TableWidget(
t,
state=u,
on_press=self._select,
)
widget.edit_mode = getattr(self, "edit_mode", False)
widget.size_hint = (None, None)
widget.size = (dp(t.width), dp(t.height))
widget.pos = (
dp(t.pos_x),
dp(t.pos_y)
)
self.map_area.add_widget(widget)
# ---------------- HELPERS ----------------
def _get_room_name(self, room):
return getattr(room, "room_name", None) or getattr(room, "name", "Room")
# ---------------- SELECT ----------------
def _select(self, table_id):
if self.on_select:
self.on_select(table_id)
class MapaEditor(Screen):
def __init__(self, controller, back_screen, **kwargs):
super().__init__(**kwargs)
self.controller = controller
self.back_screen = back_screen
# ===== ROOT =====
root = BoxLayout(orientation="vertical")
# ===== MAPA =====
provider = controller.get_table_map_provider()
self.map_widget = RoomTableMapWidget(
provider=provider,
on_select=None,
)
self.map_widget.edit_mode = True
root.add_widget(self.map_widget)
# ===== TOOLBAR DOLE =====
toolbar = BoxLayout(
size_hint_y=None,
height=dp(70),
spacing=5,
padding=5,
)
btn_add = Button(text="+ Stůl")
btn_add_room = Button(text="+ Místnost")
btn_save_room = Button(text="Uložit\nmístnost")
btn_delete_room = Button(text="Smazat\nmístnost")
btn_load_room = Button(text="Načíst\nmístnost")
btn_save_all = Button(text="Uložit vše\nna server")
btn_back = Button(text="Zpět uložit\njen do paměti")
btn_cancel = Button(text="Zrušit")
# bindy
btn_add.bind(on_press=self._add_table)
btn_add_room.bind(on_press=self._add_room)
btn_save_room.bind(on_press=self._save_room)
btn_delete_room.bind(on_press=self._delete_room)
btn_load_room.bind(on_press=self._load_room_dialog)
btn_save_all.bind(on_press=self._save_all)
btn_back.bind(on_press=self._go_back)
btn_cancel.bind(on_press=self._cancel)
# přidání tlačítek
toolbar.add_widget(btn_add)
toolbar.add_widget(btn_add_room)
toolbar.add_widget(btn_save_room)
toolbar.add_widget(btn_delete_room)
toolbar.add_widget(btn_load_room)
toolbar.add_widget(btn_save_all)
toolbar.add_widget(btn_back)
toolbar.add_widget(btn_cancel)
root.add_widget(toolbar)
# ===== FINAL =====
self.add_widget(root)
# načtení mapy
self.map_widget.refresh()
def _go_back(self, *_):
from kivy.app import App
self._save_room()
app = App.get_running_app()
main = app.root.get_screen(self.back_screen)
if hasattr(main, "map_widget"):
main.map_widget.refresh()
app.root.current = self.back_screen
app.root.remove_widget(self)
def _delete_room(self, *_):
room_name = self.map_widget.current_room
if not room_name:
print("Není vybraná místnost")
return
# ===== potvrzovací dialog =====
layout = BoxLayout(orientation="vertical", spacing=10, padding=10)
layout.add_widget(Label(
text=f"Opravdu smazat místnost?\n\n{room_name}",
halign="center"
))
btn_yes = Button(text="ANO smazat")
btn_no = Button(text="Ne")
layout.add_widget(btn_yes)
layout.add_widget(btn_no)
popup = Popup(
title="Smazání místnosti",
content=layout,
size_hint=(0.5, 0.4),
auto_dismiss=False,
)
def do_delete(_):
rooms = self.controller.mapa_stolu.rooms
# ===== smaž místnost =====
new_rooms = []
for r in rooms:
rn = r["room_name"] if isinstance(r, dict) else r.room_name
if rn != room_name:
new_rooms.append(r)
self.controller.mapa_stolu.rooms = new_rooms
# ===== přepnutí na jinou =====
if new_rooms:
first = new_rooms[0]
self.map_widget.current_room = (
first["room_name"] if isinstance(first, dict) else first.room_name
)
else:
self.map_widget.current_room = None
self.map_widget.refresh()
popup.dismiss()
print(f"Smazána místnost: {room_name}")
btn_yes.bind(on_press=do_delete)
btn_no.bind(on_press=lambda *_: popup.dismiss())
popup.open()
def _add_room(self, *_):
layout = BoxLayout(orientation="vertical", spacing=5, padding=5)
name_in = TextInput(
text="Nová místnost",
multiline=False,
)
btn_ok = Button(text="Vytvořit")
btn_cancel = Button(text="Zrušit")
layout.add_widget(Label(text="Název místnosti"))
layout.add_widget(name_in)
layout.add_widget(btn_ok)
layout.add_widget(btn_cancel)
popup = Popup(
title="Nová místnost",
content=layout,
size_hint=(0.5, 0.4),
auto_dismiss=False,
)
def create(_):
name = name_in.text.strip()
if not name:
return
# ===== kontrola duplicity =====
for r in self.controller.mapa_stolu.rooms:
rn = r["room_name"] if isinstance(r, dict) else r.room_name
if rn == name:
print("Místnost již existuje")
return
new_room = data.Room(
room_name=name,
stoly=[],
)
# ===== přidání =====
self.controller.mapa_stolu.rooms.append(new_room)
# ===== přepnutí =====
self.map_widget.current_room = name
self.map_widget.refresh()
popup.dismiss()
print(f"Vytvořena místnost: {name}")
btn_ok.bind(on_press=create)
btn_cancel.bind(on_press=lambda *_: popup.dismiss())
popup.open()
def _add_table(self, *_):
room = self.map_widget.current_room or "Room"
# ===== existující ID v aktuální místnosti =====
existing_ids = {
str(w.table.id).split("|")[-1]
for w in self.map_widget.map_area.children
}
i = 1
while str(i) in existing_ids:
i += 1
short_id = str(i)
new_id = f"{room}|{short_id}"
# ===== vytvoření stolu =====
t = data.Table(
id=new_id,
name=f"Stůl {short_id}",
pos_x=10,
pos_y=MAP_H - 120,
width=100,
height=100,
radius=0.2,
)
# ===== widget =====
w = TableWidget(t, state=None, on_press=None)
w.edit_mode = True
w.size_hint = (None, None)
w.size = (dp(t.width), dp(t.height))
w.pos = (dp(t.pos_x), dp(t.pos_y))
self.map_widget.map_area.add_widget(w)
w._update()
print(f"Přidán stůl {new_id}")
def _save_room(self, *_):
room_name = self.map_widget.current_room
if not room_name:
print("Není vybraná místnost")
return
tables = []
for w in reversed(self.map_widget.map_area.children):
t = w.table
t.pos_x = int(w.pos[0] / Metrics.density)
t.pos_y = int(w.pos[1] / Metrics.density)
t.width = int(w.size[0] / Metrics.density)
t.height = int(w.size[1] / Metrics.density)
tables.append(t)
for room in self.controller.mapa_stolu.rooms:
room_name_value = room.room_name if hasattr(room, "room_name") else room["room_name"]
if room_name_value == room_name:
if hasattr(room, "stoly"):
room.stoly = tables
else:
room["stoly"] = tables
break
print(f"Uložena místnost: {room_name}")
def _save_all(self, *_):
from ui_utils import _popup_info
try:
self._save_room()
ok, resp = self.controller.save_mapa_stolu()
if ok:
_popup_info("Mapa", "Mapa uložena na server")
else:
_popup_info("Chyba", str(resp))
except Exception as e:
_popup_info("Chyba", str(e))
def _cancel(self, *_):
from kivy.app import App
app = App.get_running_app()
app.root.current = self.back_screen
app.root.remove_widget(self)
def _load_room_dialog(self, *_):
btn_h = dp(50)
# ===== ROOT =====
root = BoxLayout(
orientation="vertical",
spacing=5,
padding=5,
)
popup = Popup(
title="Načíst místnost",
content=root,
size_hint=(0.5, 0.6),
auto_dismiss=False,
)
# FIXNÍ ZPĚT
btn_back = Button(
text="Zpět",
size_hint_y=None,
height=btn_h,
)
btn_back.bind(on_press=popup.dismiss)
root.add_widget(btn_back)
# SCROLL MÍSTNOSTI
scroll = ScrollView(
size_hint=(1, 1),
do_scroll_y=True,
do_scroll_x=False,
bar_width=dp(10),
)
room_list = BoxLayout(
orientation="vertical",
size_hint_y=None,
spacing=5,
)
room_list.bind(minimum_height=room_list.setter("height"))
for room in self.controller.mapa_stolu.rooms:
room_name = room["room_name"] if isinstance(room, dict) else room.room_name
btn = Button(
text=room_name,
size_hint_y=None,
height=btn_h,
)
def load_room(instance, name=room_name):
self.map_widget.current_room = name
self.map_widget.refresh()
popup.dismiss()
btn.bind(on_press=load_room)
room_list.add_widget(btn)
scroll.add_widget(room_list)
root.add_widget(scroll)
popup.open()