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()