updated to video creation
This commit is contained in:
889
screens/pause_edit_screen_improved.py
Normal file
889
screens/pause_edit_screen_improved.py
Normal file
@@ -0,0 +1,889 @@
|
||||
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()
|
||||
Reference in New Issue
Block a user