890 lines
33 KiB
Python
890 lines
33 KiB
Python
import kivy
|
|
from kivy.uix.screenmanager import Screen
|
|
from kivy.uix.boxlayout import BoxLayout
|
|
from kivy.uix.label import Label
|
|
from kivy.uix.button import Button
|
|
from kivy.uix.textinput import TextInput
|
|
from kivy.uix.filechooser import FileChooserIconView
|
|
from kivy.uix.widget import Widget
|
|
from kivy.uix.image import Image
|
|
from kivy.uix.carousel import Carousel
|
|
from kivy.uix.progressbar import ProgressBar
|
|
from kivy.graphics import Color, Rectangle, Line
|
|
from kivy.uix.scrollview import ScrollView
|
|
from kivy.uix.popup import Popup
|
|
from kivy.uix.gridlayout import GridLayout
|
|
from kivy.properties import StringProperty
|
|
from kivy.clock import Clock
|
|
import os
|
|
import json
|
|
import shutil
|
|
import threading
|
|
from geopy.geocoders import Nominatim
|
|
from config import RESOURCES_FOLDER
|
|
|
|
class PauseEditScreen(Screen):
|
|
project_name = StringProperty("")
|
|
|
|
def __init__(self, **kwargs):
|
|
super().__init__(**kwargs)
|
|
self.pauses = []
|
|
self.on_save_callback = None
|
|
self.loading_popup = None
|
|
self.carousel = None
|
|
|
|
def on_pre_enter(self):
|
|
"""Called when entering the screen"""
|
|
self.show_loading_popup()
|
|
# Delay the layout building to show loading popup first
|
|
Clock.schedule_once(self.start_loading_process, 0.1)
|
|
|
|
def show_loading_popup(self):
|
|
"""Show loading popup while building the layout"""
|
|
layout = BoxLayout(orientation='vertical', spacing=20, padding=20)
|
|
|
|
# Loading animation/progress bar
|
|
progress = ProgressBar(
|
|
max=100,
|
|
size_hint_y=None,
|
|
height=20
|
|
)
|
|
|
|
# Animate the progress bar
|
|
def animate_progress(dt):
|
|
if progress.value < 95:
|
|
progress.value += 5
|
|
else:
|
|
progress.value = 10 # Reset for continuous animation
|
|
|
|
Clock.schedule_interval(animate_progress, 0.1)
|
|
|
|
loading_label = Label(
|
|
text="Loading pause information...\nPlease wait",
|
|
color=(1, 1, 1, 1),
|
|
font_size=16,
|
|
halign="center",
|
|
text_size=(300, None)
|
|
)
|
|
|
|
layout.add_widget(loading_label)
|
|
layout.add_widget(progress)
|
|
|
|
self.loading_popup = Popup(
|
|
title="Loading Pauses",
|
|
content=layout,
|
|
size_hint=(0.8, 0.3),
|
|
auto_dismiss=False
|
|
)
|
|
self.loading_popup.open()
|
|
|
|
def start_loading_process(self, dt):
|
|
"""Start the loading process in background"""
|
|
# Run the heavy loading in a separate thread
|
|
thread = threading.Thread(target=self.load_data_background)
|
|
thread.daemon = True
|
|
thread.start()
|
|
|
|
def load_data_background(self):
|
|
"""Load pause data in background thread"""
|
|
try:
|
|
# Load pauses
|
|
self.load_pauses()
|
|
|
|
# Pre-process location suggestions to speed up UI
|
|
for pause in self.pauses:
|
|
lat = pause["location"]["latitude"]
|
|
lon = pause["location"]["longitude"]
|
|
# Cache the location suggestion
|
|
if 'location_suggestion' not in pause:
|
|
pause['location_suggestion'] = self.suggest_location_name(lat, lon)
|
|
|
|
# Schedule UI update on main thread
|
|
Clock.schedule_once(self.finish_loading, 0)
|
|
|
|
except Exception as e:
|
|
print(f"Error loading pause data: {e}")
|
|
Clock.schedule_once(self.finish_loading, 0)
|
|
|
|
def finish_loading(self, dt):
|
|
"""Finish loading and build the UI"""
|
|
try:
|
|
self.build_pause_layout()
|
|
finally:
|
|
# Close loading popup
|
|
if self.loading_popup:
|
|
self.loading_popup.dismiss()
|
|
self.loading_popup = None
|
|
|
|
def suggest_location_name(self, lat, lon):
|
|
"""Simplified location suggestion"""
|
|
try:
|
|
geolocator = Nominatim(user_agent="traccar_animation")
|
|
location = geolocator.reverse((lat, lon), exactly_one=True, timeout=8, addressdetails=True)
|
|
|
|
if location and location.raw:
|
|
address = location.raw.get('address', {})
|
|
|
|
# Look for street address first
|
|
if 'road' in address:
|
|
road = address['road']
|
|
if 'house_number' in address:
|
|
return f"{road} {address['house_number']}"
|
|
return road
|
|
|
|
# Look for area name
|
|
for key in ['neighbourhood', 'suburb', 'village', 'town', 'city']:
|
|
if key in address and address[key]:
|
|
return address[key]
|
|
|
|
return f"Location {lat:.4f}, {lon:.4f}"
|
|
|
|
except Exception:
|
|
return f"Location {lat:.4f}, {lon:.4f}"
|
|
|
|
def load_pauses(self):
|
|
"""Load pauses from the project folder"""
|
|
project_folder = os.path.join(RESOURCES_FOLDER, "projects", self.project_name)
|
|
pauses_path = os.path.join(project_folder, "pauses.json")
|
|
|
|
if os.path.exists(pauses_path):
|
|
with open(pauses_path, "r") as f:
|
|
self.pauses = json.load(f)
|
|
else:
|
|
self.pauses = []
|
|
|
|
def save_pauses(self):
|
|
"""Save pauses to the project folder"""
|
|
project_folder = os.path.join(RESOURCES_FOLDER, "projects", self.project_name)
|
|
pauses_path = os.path.join(project_folder, "pauses.json")
|
|
|
|
with open(pauses_path, "w") as f:
|
|
json.dump(self.pauses, f, indent=2)
|
|
|
|
def build_pause_layout(self):
|
|
"""Build the main pause editing layout with carousel for multiple pauses"""
|
|
self.clear_widgets()
|
|
|
|
# Main layout with dark background
|
|
main_layout = BoxLayout(orientation='vertical', spacing=5, padding=[5, 5, 5, 5])
|
|
with main_layout.canvas.before:
|
|
Color(0.11, 0.10, 0.15, 1)
|
|
main_layout.bg_rect = Rectangle(pos=main_layout.pos, size=main_layout.size)
|
|
main_layout.bind(pos=self.update_bg_rect, size=self.update_bg_rect)
|
|
|
|
# Header with back button and pause counter
|
|
header = BoxLayout(orientation='horizontal', size_hint_y=None, height=40, spacing=10)
|
|
|
|
back_btn = Button(
|
|
text="← Back",
|
|
size_hint_x=None,
|
|
width=70,
|
|
font_size=14,
|
|
background_color=(0.341, 0.235, 0.980, 1),
|
|
color=(1, 1, 1, 1)
|
|
)
|
|
back_btn.bind(on_press=self.go_back)
|
|
|
|
# Dynamic title based on number of pauses
|
|
pause_count = len(self.pauses)
|
|
if pause_count > 2:
|
|
title_text = f"Edit Pauses ({pause_count} total)\nSwipe to navigate"
|
|
else:
|
|
title_text = "Edit Pauses"
|
|
|
|
title_label = Label(
|
|
text=title_text,
|
|
font_size=14 if pause_count > 2 else 16,
|
|
color=(1, 1, 1, 1),
|
|
halign="center",
|
|
bold=True
|
|
)
|
|
|
|
header.add_widget(back_btn)
|
|
header.add_widget(title_label)
|
|
header.add_widget(Widget(size_hint_x=None, width=70))
|
|
|
|
main_layout.add_widget(header)
|
|
|
|
# Choose layout based on number of pauses
|
|
if pause_count > 2:
|
|
# Use carousel for multiple pauses
|
|
content_area = self.create_carousel_layout()
|
|
else:
|
|
# Use simple scroll view for 1-2 pauses
|
|
content_area = self.create_simple_scroll_layout()
|
|
|
|
main_layout.add_widget(content_area)
|
|
|
|
# Save all button at bottom
|
|
save_all_btn = Button(
|
|
text="Save All Changes & Go Back",
|
|
size_hint_y=None,
|
|
height=45,
|
|
font_size=14,
|
|
background_color=(0.2, 0.7, 0.2, 1),
|
|
color=(1, 1, 1, 1),
|
|
bold=True
|
|
)
|
|
save_all_btn.bind(on_press=self.save_all_and_close)
|
|
main_layout.add_widget(save_all_btn)
|
|
|
|
self.add_widget(main_layout)
|
|
|
|
def create_carousel_layout(self):
|
|
"""Create carousel layout for multiple pauses"""
|
|
# Create carousel
|
|
self.carousel = Carousel(
|
|
direction='right',
|
|
loop=True,
|
|
size_hint=(1, 1)
|
|
)
|
|
|
|
# Add each pause as a slide
|
|
for idx, pause in enumerate(self.pauses):
|
|
# Create a slide container
|
|
slide = BoxLayout(orientation='vertical', spacing=5, padding=[5, 5, 5, 5])
|
|
|
|
# Add pause indicator
|
|
indicator = Label(
|
|
text=f"Pause {idx + 1} of {len(self.pauses)} - Swipe for more",
|
|
font_size=12,
|
|
color=(0.8, 0.8, 0.8, 1),
|
|
size_hint_y=None,
|
|
height=25,
|
|
halign="center"
|
|
)
|
|
slide.add_widget(indicator)
|
|
|
|
# Create pause frame
|
|
pause_frame = self.create_pause_frame(idx, pause)
|
|
|
|
# Wrap in scroll view for this slide
|
|
scroll = ScrollView(size_hint=(1, 1))
|
|
scroll_content = BoxLayout(orientation='vertical', size_hint_y=None, padding=[2, 2, 2, 2])
|
|
scroll_content.bind(minimum_height=scroll_content.setter('height'))
|
|
scroll_content.add_widget(pause_frame)
|
|
scroll.add_widget(scroll_content)
|
|
|
|
slide.add_widget(scroll)
|
|
self.carousel.add_widget(slide)
|
|
|
|
return self.carousel
|
|
|
|
def create_simple_scroll_layout(self):
|
|
"""Create simple scroll layout for 1-2 pauses"""
|
|
scroll_content = BoxLayout(orientation='vertical', spacing=10, size_hint_y=None, padding=[5, 5, 5, 5])
|
|
scroll_content.bind(minimum_height=scroll_content.setter('height'))
|
|
|
|
for idx, pause in enumerate(self.pauses):
|
|
pause_frame = self.create_pause_frame(idx, pause)
|
|
scroll_content.add_widget(pause_frame)
|
|
|
|
scroll = ScrollView(size_hint=(1, 1))
|
|
scroll.add_widget(scroll_content)
|
|
return scroll
|
|
|
|
def update_bg_rect(self, instance, value):
|
|
"""Update background rectangle"""
|
|
instance.bg_rect.pos = instance.pos
|
|
instance.bg_rect.size = instance.size
|
|
|
|
def create_pause_frame(self, idx, pause):
|
|
"""Create a frame for a single pause"""
|
|
# Main frame with border
|
|
frame = BoxLayout(
|
|
orientation='vertical',
|
|
spacing=8,
|
|
padding=[8, 8, 8, 8],
|
|
size_hint_y=None,
|
|
height=380 # Increased height for better photo scrolling
|
|
)
|
|
|
|
with frame.canvas.before:
|
|
Color(0.18, 0.18, 0.22, 1) # Frame background
|
|
frame.bg_rect = Rectangle(pos=frame.pos, size=frame.size)
|
|
Color(0.4, 0.6, 1.0, 1) # Frame border
|
|
frame.border_line = Line(rectangle=(frame.x, frame.y, frame.width, frame.height), width=2)
|
|
|
|
def update_frame(instance, value, frame_widget=frame):
|
|
frame_widget.bg_rect.pos = frame_widget.pos
|
|
frame_widget.bg_rect.size = frame_widget.size
|
|
frame_widget.border_line.rectangle = (frame_widget.x, frame_widget.y, frame_widget.width, frame_widget.height)
|
|
frame.bind(pos=update_frame, size=update_frame)
|
|
|
|
# 1. Pause number label (centered)
|
|
pause_number_label = Label(
|
|
text=f"[b]PAUSE {idx + 1}[/b]",
|
|
markup=True,
|
|
font_size=16,
|
|
color=(1, 1, 1, 1),
|
|
size_hint_y=None,
|
|
height=30,
|
|
halign="center",
|
|
valign="middle"
|
|
)
|
|
pause_number_label.bind(width=lambda instance, value: setattr(instance, 'text_size', (value, None)))
|
|
frame.add_widget(pause_number_label)
|
|
|
|
# 2. Location suggestion (left aligned) - use cached version if available
|
|
suggested_place = pause.get('location_suggestion') or self.suggest_location_name(
|
|
pause["location"]["latitude"], pause["location"]["longitude"]
|
|
)
|
|
|
|
location_label = Label(
|
|
text=f"Location: {suggested_place}",
|
|
font_size=12,
|
|
color=(0.8, 0.9, 1, 1),
|
|
size_hint_y=None,
|
|
height=25,
|
|
halign="left",
|
|
valign="middle"
|
|
)
|
|
location_label.bind(width=lambda instance, value: setattr(instance, 'text_size', (value, None)))
|
|
frame.add_widget(location_label)
|
|
|
|
# 3. Custom name entry and save button
|
|
name_layout = BoxLayout(orientation='horizontal', spacing=5, size_hint_y=None, height=35)
|
|
|
|
name_input = TextInput(
|
|
text=pause.get('name', ''),
|
|
hint_text="Enter custom location name...",
|
|
multiline=False,
|
|
background_color=(0.25, 0.25, 0.3, 1),
|
|
foreground_color=(1, 1, 1, 1),
|
|
font_size=12,
|
|
padding=[8, 8, 8, 8]
|
|
)
|
|
|
|
save_name_btn = Button(
|
|
text="Save Name",
|
|
size_hint_x=None,
|
|
width=80,
|
|
font_size=11,
|
|
background_color=(0.341, 0.235, 0.980, 1),
|
|
color=(1, 1, 1, 1)
|
|
)
|
|
save_name_btn.bind(on_press=lambda x: self.save_pause_name(pause, name_input))
|
|
|
|
name_layout.add_widget(name_input)
|
|
name_layout.add_widget(save_name_btn)
|
|
frame.add_widget(name_layout)
|
|
|
|
# 4. Photos area - vertical scrolling
|
|
photos_area = self.create_photos_area_vertical(idx, pause)
|
|
frame.add_widget(photos_area)
|
|
|
|
# 5. Save and Delete buttons row
|
|
button_layout = BoxLayout(orientation='horizontal', spacing=5, size_hint_y=None, height=30)
|
|
|
|
save_pause_btn = Button(
|
|
text="Save Pause Info",
|
|
font_size=12,
|
|
background_color=(0.2, 0.6, 0.8, 1),
|
|
color=(1, 1, 1, 1)
|
|
)
|
|
save_pause_btn.bind(on_press=lambda x: self.save_individual_pause(idx))
|
|
|
|
delete_pause_btn = Button(
|
|
text="Delete Pause",
|
|
font_size=12,
|
|
background_color=(0.8, 0.2, 0.2, 1),
|
|
color=(1, 1, 1, 1)
|
|
)
|
|
delete_pause_btn.bind(on_press=lambda x: self.delete_pause(idx))
|
|
|
|
button_layout.add_widget(save_pause_btn)
|
|
button_layout.add_widget(delete_pause_btn)
|
|
frame.add_widget(button_layout)
|
|
|
|
return frame
|
|
|
|
def create_photos_area_vertical(self, pause_idx, pause):
|
|
"""Create photos area with vertical scrolling"""
|
|
photos_layout = BoxLayout(orientation='vertical', spacing=5, size_hint_y=None, height=200)
|
|
|
|
# Photos header with add button
|
|
photos_header = BoxLayout(orientation='horizontal', size_hint_y=None, height=30)
|
|
|
|
photos_title = Label(
|
|
text="Photos:",
|
|
font_size=12,
|
|
color=(1, 1, 1, 1),
|
|
size_hint_x=0.5,
|
|
halign="left",
|
|
valign="middle"
|
|
)
|
|
photos_title.bind(width=lambda instance, value: setattr(instance, 'text_size', (value, None)))
|
|
|
|
add_photos_btn = Button(
|
|
text="Add Photos",
|
|
size_hint_x=0.5,
|
|
font_size=11,
|
|
background_color=(0.2, 0.7, 0.2, 1),
|
|
color=(1, 1, 1, 1)
|
|
)
|
|
add_photos_btn.bind(on_press=lambda x: self.add_photos(pause_idx))
|
|
|
|
photos_header.add_widget(photos_title)
|
|
photos_header.add_widget(add_photos_btn)
|
|
photos_layout.add_widget(photos_header)
|
|
|
|
# Get photos for this pause
|
|
project_folder = os.path.join(RESOURCES_FOLDER, "projects", self.project_name)
|
|
pause_img_folder = os.path.join(project_folder, f"pause_{pause_idx+1}")
|
|
os.makedirs(pause_img_folder, exist_ok=True)
|
|
|
|
img_list = [f for f in os.listdir(pause_img_folder) if os.path.isfile(os.path.join(pause_img_folder, f))]
|
|
|
|
if img_list:
|
|
# Create vertical scrolling photo gallery
|
|
photos_scroll = ScrollView(size_hint=(1, 1), do_scroll_y=True, do_scroll_x=False)
|
|
photos_content = BoxLayout(orientation='vertical', spacing=2, size_hint_y=None, padding=[2, 2, 2, 2])
|
|
photos_content.bind(minimum_height=photos_content.setter('height'))
|
|
|
|
for img_file in img_list:
|
|
photo_item = self.create_vertical_photo_item(pause_idx, img_file, photos_content)
|
|
photos_content.add_widget(photo_item)
|
|
|
|
photos_scroll.add_widget(photos_content)
|
|
photos_layout.add_widget(photos_scroll)
|
|
else:
|
|
no_photos_label = Label(
|
|
text="No photos added yet",
|
|
font_size=12,
|
|
color=(0.6, 0.6, 0.6, 1),
|
|
size_hint_y=1,
|
|
halign="center"
|
|
)
|
|
photos_layout.add_widget(no_photos_label)
|
|
|
|
return photos_layout
|
|
|
|
def create_vertical_photo_item(self, pause_idx, img_file, parent_layout):
|
|
"""Create a photo item for vertical scrolling"""
|
|
# Main container with border
|
|
photo_item = BoxLayout(orientation='horizontal', spacing=5, size_hint_y=None, height=60, padding=[2, 2, 2, 2])
|
|
|
|
# Add border and background to photo item
|
|
with photo_item.canvas.before:
|
|
Color(0.25, 0.25, 0.30, 1) # Background
|
|
photo_item.bg_rect = Rectangle(pos=photo_item.pos, size=photo_item.size)
|
|
Color(0.4, 0.4, 0.5, 1) # Border
|
|
photo_item.border_line = Line(rectangle=(photo_item.x, photo_item.y, photo_item.width, photo_item.height), width=1)
|
|
|
|
def update_photo_item(instance, value, item=photo_item):
|
|
item.bg_rect.pos = item.pos
|
|
item.bg_rect.size = item.size
|
|
item.border_line.rectangle = (item.x, item.y, item.width, item.height)
|
|
photo_item.bind(pos=update_photo_item, size=update_photo_item)
|
|
|
|
# Get full path to the image
|
|
project_folder = os.path.join(RESOURCES_FOLDER, "projects", self.project_name)
|
|
pause_img_folder = os.path.join(project_folder, f"pause_{pause_idx+1}")
|
|
img_path = os.path.join(pause_img_folder, img_file)
|
|
|
|
# Image thumbnail container
|
|
image_container = BoxLayout(size_hint_x=None, width=55, padding=[2, 2, 2, 2])
|
|
|
|
try:
|
|
photo_image = Image(
|
|
source=img_path,
|
|
size_hint=(1, 1),
|
|
allow_stretch=True,
|
|
keep_ratio=True
|
|
)
|
|
except Exception:
|
|
# Fallback to a placeholder if image can't be loaded
|
|
photo_image = Widget(size_hint=(1, 1))
|
|
with photo_image.canvas:
|
|
Color(0.3, 0.3, 0.3, 1)
|
|
Rectangle(pos=photo_image.pos, size=photo_image.size)
|
|
|
|
image_container.add_widget(photo_image)
|
|
|
|
# Photo info layout (filename and details)
|
|
info_layout = BoxLayout(orientation='vertical', spacing=2, size_hint_x=0.55, padding=[5, 2, 5, 2])
|
|
|
|
# Filename label (truncate if too long)
|
|
display_name = img_file if len(img_file) <= 20 else f"{img_file[:17]}..."
|
|
filename_label = Label(
|
|
text=display_name,
|
|
font_size=9,
|
|
color=(1, 1, 1, 1),
|
|
halign="left",
|
|
valign="top",
|
|
size_hint_y=0.6,
|
|
bold=True
|
|
)
|
|
filename_label.bind(width=lambda instance, value: setattr(instance, 'text_size', (value, None)))
|
|
|
|
# File size and type info
|
|
try:
|
|
file_size = os.path.getsize(img_path)
|
|
if file_size < 1024:
|
|
size_text = f"{file_size} B"
|
|
elif file_size < 1024*1024:
|
|
size_text = f"{file_size/1024:.1f} KB"
|
|
else:
|
|
size_text = f"{file_size/(1024*1024):.1f} MB"
|
|
|
|
# Get file extension
|
|
file_ext = os.path.splitext(img_file)[1].upper().replace('.', '')
|
|
info_text = f"{file_ext} • {size_text}"
|
|
except:
|
|
info_text = "Unknown format"
|
|
|
|
size_label = Label(
|
|
text=info_text,
|
|
font_size=7,
|
|
color=(0.8, 0.8, 0.8, 1),
|
|
halign="left",
|
|
valign="bottom",
|
|
size_hint_y=0.4
|
|
)
|
|
size_label.bind(width=lambda instance, value: setattr(instance, 'text_size', (value, None)))
|
|
|
|
info_layout.add_widget(filename_label)
|
|
info_layout.add_widget(size_label)
|
|
|
|
# Button layout for actions
|
|
button_layout = BoxLayout(orientation='vertical', spacing=2, size_hint_x=0.28, padding=[2, 2, 2, 2])
|
|
|
|
# View button to show full image
|
|
view_btn = Button(
|
|
text="👁 View",
|
|
font_size=8,
|
|
background_color=(0.2, 0.6, 0.8, 1),
|
|
color=(1, 1, 1, 1),
|
|
size_hint_y=0.5
|
|
)
|
|
view_btn.bind(on_press=lambda x: self.view_full_image(img_path, img_file))
|
|
|
|
# Delete button
|
|
delete_btn = Button(
|
|
text="🗑 Del",
|
|
font_size=8,
|
|
background_color=(0.8, 0.2, 0.2, 1),
|
|
color=(1, 1, 1, 1),
|
|
size_hint_y=0.5
|
|
)
|
|
delete_btn.bind(on_press=lambda x: self.delete_single_photo(pause_idx, img_file, photo_item, parent_layout))
|
|
|
|
button_layout.add_widget(view_btn)
|
|
button_layout.add_widget(delete_btn)
|
|
|
|
# Add all components to photo item
|
|
photo_item.add_widget(image_container)
|
|
photo_item.add_widget(info_layout)
|
|
photo_item.add_widget(button_layout)
|
|
|
|
return photo_item
|
|
|
|
def delete_single_photo(self, pause_idx, img_file, photo_item, parent_layout):
|
|
"""Delete a single photo with confirmation"""
|
|
def confirm_delete():
|
|
project_folder = os.path.join(RESOURCES_FOLDER, "projects", self.project_name)
|
|
pause_img_folder = os.path.join(project_folder, f"pause_{pause_idx+1}")
|
|
file_path = os.path.join(pause_img_folder, img_file)
|
|
|
|
if os.path.exists(file_path):
|
|
os.remove(file_path)
|
|
parent_layout.remove_widget(photo_item)
|
|
parent_layout.height = max(20, len(parent_layout.children) * 62)
|
|
|
|
self.show_confirmation(
|
|
f"Delete Photo",
|
|
f"Are you sure you want to delete '{img_file}'?",
|
|
confirm_delete
|
|
)
|
|
|
|
def delete_pause(self, pause_idx):
|
|
"""Delete an entire pause with confirmation"""
|
|
def confirm_delete_pause():
|
|
try:
|
|
# Remove pause from the list
|
|
if 0 <= pause_idx < len(self.pauses):
|
|
self.pauses.pop(pause_idx)
|
|
|
|
# Save the updated pauses list
|
|
self.save_pauses()
|
|
|
|
# Remove pause folder and its contents
|
|
project_folder = os.path.join(RESOURCES_FOLDER, "projects", self.project_name)
|
|
pause_img_folder = os.path.join(project_folder, f"pause_{pause_idx+1}")
|
|
|
|
if os.path.exists(pause_img_folder):
|
|
shutil.rmtree(pause_img_folder)
|
|
|
|
# Reorganize remaining pause folders
|
|
self.reorganize_pause_folders()
|
|
|
|
# Refresh the entire layout
|
|
self.show_message("Pause Deleted", f"Pause {pause_idx + 1} has been deleted successfully!")
|
|
Clock.schedule_once(lambda dt: self.build_pause_layout(), 0.5)
|
|
|
|
except Exception as e:
|
|
self.show_message("Error", f"Failed to delete pause: {str(e)}")
|
|
|
|
self.show_confirmation(
|
|
"Delete Pause",
|
|
f"Are you sure you want to delete Pause {pause_idx + 1}?\nThis will remove the pause location and all its photos permanently.",
|
|
confirm_delete_pause
|
|
)
|
|
|
|
def reorganize_pause_folders(self):
|
|
"""Reorganize pause folders after deletion to maintain sequential numbering"""
|
|
project_folder = os.path.join(RESOURCES_FOLDER, "projects", self.project_name)
|
|
|
|
# Get all existing pause folders
|
|
existing_folders = []
|
|
for item in os.listdir(project_folder):
|
|
item_path = os.path.join(project_folder, item)
|
|
if os.path.isdir(item_path) and item.startswith("pause_"):
|
|
try:
|
|
folder_num = int(item.split("_")[1])
|
|
existing_folders.append((folder_num, item_path))
|
|
except (IndexError, ValueError):
|
|
continue
|
|
|
|
# Sort by folder number
|
|
existing_folders.sort(key=lambda x: x[0])
|
|
|
|
# Rename folders to be sequential starting from 1
|
|
temp_folders = []
|
|
for i, (old_num, old_path) in enumerate(existing_folders):
|
|
new_num = i + 1
|
|
if old_num != new_num:
|
|
# Create temporary name to avoid conflicts
|
|
temp_path = os.path.join(project_folder, f"temp_pause_{new_num}")
|
|
os.rename(old_path, temp_path)
|
|
temp_folders.append((temp_path, new_num))
|
|
else:
|
|
temp_folders.append((old_path, new_num))
|
|
|
|
# Final rename to correct names
|
|
for temp_path, new_num in temp_folders:
|
|
final_path = os.path.join(project_folder, f"pause_{new_num}")
|
|
if temp_path != final_path:
|
|
if os.path.exists(final_path):
|
|
shutil.rmtree(final_path)
|
|
os.rename(temp_path, final_path)
|
|
|
|
def save_pause_name(self, pause, name_input):
|
|
"""Save the custom name for a pause"""
|
|
pause['name'] = name_input.text
|
|
|
|
def save_individual_pause(self, pause_idx):
|
|
"""Save individual pause info"""
|
|
self.save_pauses()
|
|
# Show confirmation
|
|
self.show_message("Pause Saved", f"Pause {pause_idx + 1} information saved successfully!")
|
|
|
|
def add_photos(self, pause_idx):
|
|
"""Open file browser to add photos"""
|
|
project_folder = os.path.join(RESOURCES_FOLDER, "projects", self.project_name)
|
|
pause_img_folder = os.path.join(project_folder, f"pause_{pause_idx+1}")
|
|
|
|
layout = BoxLayout(orientation='vertical', spacing=10, padding=10)
|
|
with layout.canvas.before:
|
|
Color(0.13, 0.13, 0.16, 1)
|
|
layout.bg_rect = Rectangle(pos=layout.pos, size=layout.size)
|
|
layout.bind(
|
|
pos=lambda inst, val: setattr(layout.bg_rect, 'pos', inst.pos),
|
|
size=lambda inst, val: setattr(layout.bg_rect, 'size', inst.size)
|
|
)
|
|
|
|
title_label = Label(
|
|
text=f"Select photos for Pause {pause_idx + 1}:",
|
|
color=(1, 1, 1, 1),
|
|
font_size=14,
|
|
size_hint_y=None,
|
|
height=30
|
|
)
|
|
|
|
filechooser = FileChooserIconView(
|
|
filters=['*.png', '*.jpg', '*.jpeg', '*.gif', '*.bmp'],
|
|
path=os.path.expanduser('~'),
|
|
multiselect=True
|
|
)
|
|
|
|
btn_layout = BoxLayout(orientation='horizontal', spacing=10, size_hint_y=None, height=40)
|
|
|
|
add_btn = Button(
|
|
text="Add Selected",
|
|
background_color=(0.2, 0.7, 0.2, 1),
|
|
color=(1, 1, 1, 1),
|
|
font_size=12
|
|
)
|
|
|
|
cancel_btn = Button(
|
|
text="Cancel",
|
|
background_color=(0.6, 0.3, 0.3, 1),
|
|
color=(1, 1, 1, 1),
|
|
font_size=12
|
|
)
|
|
|
|
btn_layout.add_widget(cancel_btn)
|
|
btn_layout.add_widget(add_btn)
|
|
|
|
layout.add_widget(title_label)
|
|
layout.add_widget(filechooser)
|
|
layout.add_widget(btn_layout)
|
|
|
|
popup = Popup(
|
|
title="Add Photos",
|
|
content=layout,
|
|
size_hint=(0.95, 0.9),
|
|
auto_dismiss=False
|
|
)
|
|
|
|
def add_selected_files(instance):
|
|
if filechooser.selection:
|
|
for file_path in filechooser.selection:
|
|
if os.path.isfile(file_path):
|
|
filename = os.path.basename(file_path)
|
|
dest_path = os.path.join(pause_img_folder, filename)
|
|
if not os.path.exists(dest_path):
|
|
shutil.copy2(file_path, dest_path)
|
|
Clock.schedule_once(lambda dt: self.refresh_photos_display(), 0.1)
|
|
popup.dismiss()
|
|
else:
|
|
popup.dismiss()
|
|
|
|
add_btn.bind(on_press=add_selected_files)
|
|
cancel_btn.bind(on_press=lambda x: popup.dismiss())
|
|
popup.open()
|
|
|
|
def refresh_photos_display(self):
|
|
"""Refresh the entire display to show updated photos"""
|
|
self.build_pause_layout()
|
|
|
|
def show_message(self, title, message):
|
|
"""Show a simple message popup"""
|
|
layout = BoxLayout(orientation='vertical', spacing=10, padding=10)
|
|
|
|
msg_label = Label(
|
|
text=message,
|
|
color=(1, 1, 1, 1),
|
|
font_size=12,
|
|
halign="center"
|
|
)
|
|
msg_label.bind(width=lambda instance, value: setattr(instance, 'text_size', (value, None)))
|
|
|
|
ok_btn = Button(
|
|
text="OK",
|
|
size_hint_y=None,
|
|
height=35,
|
|
background_color=(0.341, 0.235, 0.980, 1),
|
|
color=(1, 1, 1, 1)
|
|
)
|
|
|
|
layout.add_widget(msg_label)
|
|
layout.add_widget(ok_btn)
|
|
|
|
popup = Popup(title=title, content=layout, size_hint=(0.8, 0.4))
|
|
ok_btn.bind(on_press=lambda x: popup.dismiss())
|
|
popup.open()
|
|
|
|
def show_confirmation(self, title, message, confirm_callback):
|
|
"""Show a confirmation dialog"""
|
|
layout = BoxLayout(orientation='vertical', spacing=10, padding=10)
|
|
|
|
msg_label = Label(
|
|
text=message,
|
|
color=(1, 1, 1, 1),
|
|
font_size=12,
|
|
halign="center"
|
|
)
|
|
msg_label.bind(width=lambda instance, value: setattr(instance, 'text_size', (value, None)))
|
|
|
|
btn_layout = BoxLayout(orientation='horizontal', spacing=10, size_hint_y=None, height=35)
|
|
|
|
cancel_btn = Button(
|
|
text="Cancel",
|
|
background_color=(0.6, 0.3, 0.3, 1),
|
|
color=(1, 1, 1, 1)
|
|
)
|
|
|
|
confirm_btn = Button(
|
|
text="Confirm",
|
|
background_color=(0.8, 0.2, 0.2, 1),
|
|
color=(1, 1, 1, 1)
|
|
)
|
|
|
|
btn_layout.add_widget(cancel_btn)
|
|
btn_layout.add_widget(confirm_btn)
|
|
|
|
layout.add_widget(msg_label)
|
|
layout.add_widget(btn_layout)
|
|
|
|
popup = Popup(title=title, content=layout, size_hint=(0.8, 0.4), auto_dismiss=False)
|
|
|
|
cancel_btn.bind(on_press=lambda x: popup.dismiss())
|
|
confirm_btn.bind(on_press=lambda x: (confirm_callback(), popup.dismiss()))
|
|
|
|
popup.open()
|
|
|
|
def save_all_and_close(self, instance):
|
|
"""Save all pauses and return to previous screen"""
|
|
self.save_pauses()
|
|
if self.on_save_callback:
|
|
self.on_save_callback()
|
|
self.go_back()
|
|
|
|
def go_back(self, instance=None):
|
|
"""Return to the previous screen"""
|
|
self.manager.current = "create_animation"
|
|
|
|
def set_project_and_callback(self, project_name, callback=None):
|
|
"""Set the project name and callback for this screen"""
|
|
self.project_name = project_name
|
|
self.on_save_callback = callback
|
|
|
|
def view_full_image(self, img_path, img_file):
|
|
"""Show full image in a popup"""
|
|
layout = BoxLayout(orientation='vertical', spacing=10, padding=10)
|
|
with layout.canvas.before:
|
|
Color(0.05, 0.05, 0.08, 1)
|
|
layout.bg_rect = Rectangle(pos=layout.pos, size=layout.size)
|
|
layout.bind(
|
|
pos=lambda inst, val: setattr(layout.bg_rect, 'pos', inst.pos),
|
|
size=lambda inst, val: setattr(layout.bg_rect, 'size', inst.size)
|
|
)
|
|
|
|
# Image display
|
|
try:
|
|
full_image = Image(
|
|
source=img_path,
|
|
allow_stretch=True,
|
|
keep_ratio=True
|
|
)
|
|
except Exception:
|
|
full_image = Label(
|
|
text="Unable to load image",
|
|
color=(1, 1, 1, 1),
|
|
font_size=16
|
|
)
|
|
|
|
# Close button
|
|
close_btn = Button(
|
|
text="Close",
|
|
size_hint_y=None,
|
|
height=40,
|
|
background_color=(0.341, 0.235, 0.980, 1),
|
|
color=(1, 1, 1, 1),
|
|
font_size=14
|
|
)
|
|
|
|
layout.add_widget(full_image)
|
|
layout.add_widget(close_btn)
|
|
|
|
popup = Popup(
|
|
title=f"Photo: {img_file}",
|
|
content=layout,
|
|
size_hint=(0.95, 0.95),
|
|
auto_dismiss=False
|
|
)
|
|
|
|
close_btn.bind(on_press=lambda x: popup.dismiss())
|
|
popup.open()
|