- Added pencil edit button to player controls - Created EditPopup with drawing layer for image annotation - Drawing tools: color selection (red/blue/green/black), thickness control - Features: undo last stroke, clear all strokes, save edited image - Playback automatically pauses during editing - Only images (.jpg, .jpeg, .png, .bmp) can be edited - Edited images saved with '_edited' suffix in same directory - Drawing layer with touch support for annotations - Full toolbar with color, thickness, and action controls
568 lines
19 KiB
Plaintext
568 lines
19 KiB
Plaintext
#:kivy 2.1.0
|
|
|
|
# Custom On-Screen Keyboard Widget
|
|
<CustomKeyboard@FloatLayout>:
|
|
size_hint: None, None
|
|
width: root.parent.width * 0.5 if root.parent else dp(600)
|
|
height: self.width / 3
|
|
pos: (root.parent.width - self.width) / 2 if root.parent else 0, 0
|
|
opacity: 0
|
|
|
|
canvas.before:
|
|
Color:
|
|
rgba: 0.1, 0.1, 0.1, 0.95
|
|
RoundedRectangle:
|
|
size: self.size
|
|
pos: self.pos
|
|
radius: [dp(15), dp(15), 0, 0]
|
|
|
|
BoxLayout:
|
|
orientation: 'vertical'
|
|
padding: dp(5)
|
|
spacing: dp(5)
|
|
|
|
# Close button bar
|
|
BoxLayout:
|
|
size_hint: 1, None
|
|
height: dp(40)
|
|
padding: [dp(5), 0]
|
|
|
|
Widget:
|
|
|
|
Button:
|
|
text: '✕'
|
|
size_hint: None, 1
|
|
width: dp(40)
|
|
background_color: 0.8, 0.2, 0.2, 0.9
|
|
font_size: sp(20)
|
|
bold: True
|
|
on_press: root.parent.hide_keyboard() if root.parent and hasattr(root.parent, 'hide_keyboard') else None
|
|
|
|
# Number row
|
|
BoxLayout:
|
|
size_hint: 1, 1
|
|
spacing: dp(3)
|
|
Button:
|
|
text: '1'
|
|
on_press: root.parent.key_pressed('1') if root.parent and hasattr(root.parent, 'key_pressed') else None
|
|
Button:
|
|
text: '2'
|
|
on_press: root.parent.key_pressed('2') if root.parent and hasattr(root.parent, 'key_pressed') else None
|
|
Button:
|
|
text: '3'
|
|
on_press: root.parent.key_pressed('3') if root.parent and hasattr(root.parent, 'key_pressed') else None
|
|
Button:
|
|
text: '4'
|
|
on_press: root.parent.key_pressed('4') if root.parent and hasattr(root.parent, 'key_pressed') else None
|
|
Button:
|
|
text: '5'
|
|
on_press: root.parent.key_pressed('5') if root.parent and hasattr(root.parent, 'key_pressed') else None
|
|
Button:
|
|
text: '6'
|
|
on_press: root.parent.key_pressed('6') if root.parent and hasattr(root.parent, 'key_pressed') else None
|
|
Button:
|
|
text: '7'
|
|
on_press: root.parent.key_pressed('7') if root.parent and hasattr(root.parent, 'key_pressed') else None
|
|
Button:
|
|
text: '8'
|
|
on_press: root.parent.key_pressed('8') if root.parent and hasattr(root.parent, 'key_pressed') else None
|
|
Button:
|
|
text: '9'
|
|
on_press: root.parent.key_pressed('9') if root.parent and hasattr(root.parent, 'key_pressed') else None
|
|
Button:
|
|
text: '0'
|
|
on_press: root.parent.key_pressed('0') if root.parent and hasattr(root.parent, 'key_pressed') else None
|
|
|
|
# Top letter row (QWERTYUIOP)
|
|
BoxLayout:
|
|
size_hint: 1, 1
|
|
spacing: dp(3)
|
|
Button:
|
|
text: 'Q'
|
|
on_press: root.parent.key_pressed('q') if root.parent and hasattr(root.parent, 'key_pressed') else None
|
|
Button:
|
|
text: 'W'
|
|
on_press: root.parent.key_pressed('w') if root.parent and hasattr(root.parent, 'key_pressed') else None
|
|
Button:
|
|
text: 'E'
|
|
on_press: root.parent.key_pressed('e') if root.parent and hasattr(root.parent, 'key_pressed') else None
|
|
Button:
|
|
text: 'R'
|
|
on_press: root.parent.key_pressed('r') if root.parent and hasattr(root.parent, 'key_pressed') else None
|
|
Button:
|
|
text: 'T'
|
|
on_press: root.parent.key_pressed('t') if root.parent and hasattr(root.parent, 'key_pressed') else None
|
|
Button:
|
|
text: 'Y'
|
|
on_press: root.parent.key_pressed('y') if root.parent and hasattr(root.parent, 'key_pressed') else None
|
|
Button:
|
|
text: 'U'
|
|
on_press: root.parent.key_pressed('u') if root.parent and hasattr(root.parent, 'key_pressed') else None
|
|
Button:
|
|
text: 'I'
|
|
on_press: root.parent.key_pressed('i') if root.parent and hasattr(root.parent, 'key_pressed') else None
|
|
Button:
|
|
text: 'O'
|
|
on_press: root.parent.key_pressed('o') if root.parent and hasattr(root.parent, 'key_pressed') else None
|
|
Button:
|
|
text: 'P'
|
|
on_press: root.parent.key_pressed('p') if root.parent and hasattr(root.parent, 'key_pressed') else None
|
|
|
|
# Middle letter row (ASDFGHJKL)
|
|
BoxLayout:
|
|
size_hint: 1, 1
|
|
spacing: dp(3)
|
|
Widget:
|
|
size_hint: 0.5, 1
|
|
Button:
|
|
text: 'A'
|
|
on_press: root.parent.key_pressed('a') if root.parent and hasattr(root.parent, 'key_pressed') else None
|
|
Button:
|
|
text: 'S'
|
|
on_press: root.parent.key_pressed('s') if root.parent and hasattr(root.parent, 'key_pressed') else None
|
|
Button:
|
|
text: 'D'
|
|
on_press: root.parent.key_pressed('d') if root.parent and hasattr(root.parent, 'key_pressed') else None
|
|
Button:
|
|
text: 'F'
|
|
on_press: root.parent.key_pressed('f') if root.parent and hasattr(root.parent, 'key_pressed') else None
|
|
Button:
|
|
text: 'G'
|
|
on_press: root.parent.key_pressed('g') if root.parent and hasattr(root.parent, 'key_pressed') else None
|
|
Button:
|
|
text: 'H'
|
|
on_press: root.parent.key_pressed('h') if root.parent and hasattr(root.parent, 'key_pressed') else None
|
|
Button:
|
|
text: 'J'
|
|
on_press: root.parent.key_pressed('j') if root.parent and hasattr(root.parent, 'key_pressed') else None
|
|
Button:
|
|
text: 'K'
|
|
on_press: root.parent.key_pressed('k') if root.parent and hasattr(root.parent, 'key_pressed') else None
|
|
Button:
|
|
text: 'L'
|
|
on_press: root.parent.key_pressed('l') if root.parent and hasattr(root.parent, 'key_pressed') else None
|
|
Widget:
|
|
size_hint: 0.5, 1
|
|
|
|
# Bottom letter row (ZXCVBNM)
|
|
BoxLayout:
|
|
size_hint: 1, 1
|
|
spacing: dp(3)
|
|
Widget:
|
|
size_hint: 1, 1
|
|
Button:
|
|
text: 'Z'
|
|
on_press: root.parent.key_pressed('z') if root.parent and hasattr(root.parent, 'key_pressed') else None
|
|
Button:
|
|
text: 'X'
|
|
on_press: root.parent.key_pressed('x') if root.parent and hasattr(root.parent, 'key_pressed') else None
|
|
Button:
|
|
text: 'C'
|
|
on_press: root.parent.key_pressed('c') if root.parent and hasattr(root.parent, 'key_pressed') else None
|
|
Button:
|
|
text: 'V'
|
|
on_press: root.parent.key_pressed('v') if root.parent and hasattr(root.parent, 'key_pressed') else None
|
|
Button:
|
|
text: 'B'
|
|
on_press: root.parent.key_pressed('b') if root.parent and hasattr(root.parent, 'key_pressed') else None
|
|
Button:
|
|
text: 'N'
|
|
on_press: root.parent.key_pressed('n') if root.parent and hasattr(root.parent, 'key_pressed') else None
|
|
Button:
|
|
text: 'M'
|
|
on_press: root.parent.key_pressed('m') if root.parent and hasattr(root.parent, 'key_pressed') else None
|
|
Widget:
|
|
size_hint: 1, 1
|
|
|
|
# Bottom row (Space, Backspace)
|
|
BoxLayout:
|
|
size_hint: 1, 1
|
|
spacing: dp(3)
|
|
Button:
|
|
text: '←'
|
|
size_hint: 0.3, 1
|
|
font_size: sp(24)
|
|
on_press: root.parent.key_pressed('backspace') if root.parent and hasattr(root.parent, 'key_pressed') else None
|
|
Button:
|
|
text: 'Space'
|
|
size_hint: 0.7, 1
|
|
on_press: root.parent.key_pressed(' ') if root.parent and hasattr(root.parent, 'key_pressed') else None
|
|
|
|
<SignagePlayer@FloatLayout>:
|
|
size: root.screen_width, root.screen_height
|
|
canvas.before:
|
|
Color:
|
|
rgba: 0, 0, 0, 1
|
|
Rectangle:
|
|
size: self.size
|
|
pos: self.pos
|
|
|
|
# Main content area for images/videos
|
|
FloatLayout:
|
|
id: content_area
|
|
size: root.screen_width, root.screen_height
|
|
size_hint: None, None
|
|
pos_hint: {'x': 0, 'y': 0}
|
|
|
|
# Status label overlay (centered)
|
|
Label:
|
|
id: status_label
|
|
text: 'Loading...'
|
|
size_hint: 0.5, 0.5
|
|
size: dp(600), dp(120)
|
|
pos_hint: {'center_x': 0.5, 'center_y': 0.5}
|
|
color: 1, 1, 1, 1
|
|
font_size: sp(18)
|
|
text_size: self.size
|
|
halign: 'center'
|
|
valign: 'middle'
|
|
canvas.before:
|
|
Color:
|
|
rgba: 0, 0, 0, 0.7
|
|
RoundedRectangle:
|
|
size: self.size
|
|
pos: self.pos
|
|
radius: [dp(15)]
|
|
|
|
# New control panel overlay (bottom center, 1/6 width, 90% transparent)
|
|
BoxLayout:
|
|
id: controls_layout
|
|
orientation: 'horizontal'
|
|
size_hint: None, None
|
|
width: root.width / 6 if root.width > 0 else dp(260)
|
|
height: dp(70)
|
|
pos: (root.width - self.width) / 2, dp(10)
|
|
opacity: 1
|
|
spacing: dp(10)
|
|
padding: dp(10)
|
|
canvas.before:
|
|
Color:
|
|
rgba: 0.1, 0.1, 0.1, 0.9 # 90% transparent
|
|
RoundedRectangle:
|
|
size: self.size
|
|
pos: self.pos
|
|
radius: [dp(20)]
|
|
|
|
Button:
|
|
id: backward_btn
|
|
size_hint: None, None
|
|
size: dp(50), dp(50)
|
|
background_normal: root.resources_path + '/backward.png'
|
|
background_down: root.resources_path + '/backward.png'
|
|
border: (0, 0, 0, 0)
|
|
on_press: root.previous_media()
|
|
|
|
Button:
|
|
id: play_pause_btn
|
|
size_hint: None, None
|
|
size: dp(50), dp(50)
|
|
background_normal: root.resources_path + '/play.png'
|
|
background_down: root.resources_path + '/pause.png'
|
|
border: (0, 0, 0, 0)
|
|
on_press: root.toggle_pause()
|
|
|
|
Button:
|
|
id: edit_btn
|
|
size_hint: None, None
|
|
size: dp(50), dp(50)
|
|
background_normal: root.resources_path + '/pencil.png'
|
|
background_down: root.resources_path + '/pencil.png'
|
|
border: (0, 0, 0, 0)
|
|
on_press: root.show_edit_interface()
|
|
Button:
|
|
id: settings_btn
|
|
size_hint: None, None
|
|
size: dp(50), dp(50)
|
|
background_normal: root.resources_path + '/settings.png'
|
|
background_down: root.resources_path + '/settings.png'
|
|
border: (0, 0, 0, 0)
|
|
on_press: root.show_settings()
|
|
Button:
|
|
id: exit_btn
|
|
size_hint: None, None
|
|
size: dp(50), dp(50)
|
|
background_normal: root.resources_path + '/exit.png'
|
|
background_down: root.resources_path + '/exit.png'
|
|
border: (0, 0, 0, 0)
|
|
on_press: root.show_exit_popup()
|
|
Button:
|
|
id: forward_btn
|
|
size_hint: None, None
|
|
size: dp(50), dp(50)
|
|
background_normal: root.resources_path + '/forward.png'
|
|
background_down: root.resources_path + '/forward.png'
|
|
border: (0, 0, 0, 0)
|
|
on_press: root.next_media()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Exit password popup
|
|
<ExitPasswordPopup@Popup>:
|
|
title: 'Exit Application'
|
|
size_hint: 0.4, 0.3
|
|
auto_dismiss: False
|
|
|
|
BoxLayout:
|
|
orientation: 'vertical'
|
|
padding: dp(20)
|
|
spacing: dp(15)
|
|
|
|
Label:
|
|
text: 'Enter password to exit:'
|
|
size_hint_y: None
|
|
height: dp(30)
|
|
|
|
TextInput:
|
|
id: password_input
|
|
multiline: False
|
|
password: True
|
|
font_size: sp(16)
|
|
size_hint_y: None
|
|
height: dp(40)
|
|
write_tab: False
|
|
readonly: True
|
|
on_focus: root.on_input_focus(self, self.focus)
|
|
|
|
Label:
|
|
id: error_label
|
|
text: ''
|
|
color: 1, 0, 0, 1
|
|
size_hint_y: None
|
|
height: dp(30)
|
|
|
|
BoxLayout:
|
|
orientation: 'horizontal'
|
|
size_hint_y: None
|
|
height: dp(50)
|
|
spacing: dp(20)
|
|
|
|
Button:
|
|
text: 'Exit'
|
|
background_color: 0.6, 0.2, 0.2, 1
|
|
on_press: root.check_password()
|
|
|
|
Button:
|
|
text: 'Cancel'
|
|
background_color: 0.2, 0.6, 0.2, 1
|
|
on_press: root.dismiss()
|
|
|
|
# Settings popup content
|
|
<SettingsPopup@Popup>:
|
|
title: 'Player Settings'
|
|
size_hint: 0.8, 0.8
|
|
auto_dismiss: True
|
|
|
|
BoxLayout:
|
|
orientation: 'vertical'
|
|
padding: dp(20)
|
|
spacing: dp(15)
|
|
|
|
# Server configuration
|
|
BoxLayout:
|
|
orientation: 'horizontal'
|
|
size_hint_y: None
|
|
height: dp(40)
|
|
spacing: dp(10)
|
|
|
|
Label:
|
|
text: 'Server IP:'
|
|
size_hint_x: 0.3
|
|
text_size: self.size
|
|
halign: 'left'
|
|
valign: 'middle'
|
|
|
|
TextInput:
|
|
id: server_input
|
|
size_hint_x: 0.7
|
|
multiline: False
|
|
font_size: sp(14)
|
|
write_tab: False
|
|
on_touch_down: root.on_input_touch(self, args[1]) if self.collide_point(*args[1].pos) else None
|
|
|
|
# Screen name
|
|
BoxLayout:
|
|
orientation: 'horizontal'
|
|
size_hint_y: None
|
|
height: dp(40)
|
|
spacing: dp(10)
|
|
|
|
Label:
|
|
text: 'Screen Name:'
|
|
size_hint_x: 0.3
|
|
text_size: self.size
|
|
halign: 'left'
|
|
valign: 'middle'
|
|
|
|
TextInput:
|
|
id: screen_input
|
|
size_hint_x: 0.7
|
|
multiline: False
|
|
font_size: sp(14)
|
|
write_tab: False
|
|
on_touch_down: root.on_input_touch(self, args[1]) if self.collide_point(*args[1].pos) else None
|
|
|
|
# Quickconnect key
|
|
BoxLayout:
|
|
orientation: 'horizontal'
|
|
size_hint_y: None
|
|
height: dp(40)
|
|
spacing: dp(10)
|
|
|
|
Label:
|
|
text: 'Quickconnect:'
|
|
size_hint_x: 0.3
|
|
text_size: self.size
|
|
halign: 'left'
|
|
valign: 'middle'
|
|
|
|
TextInput:
|
|
id: quickconnect_input
|
|
size_hint_x: 0.7
|
|
multiline: False
|
|
font_size: sp(14)
|
|
write_tab: False
|
|
on_touch_down: root.on_input_touch(self, args[1]) if self.collide_point(*args[1].pos) else None
|
|
|
|
# Orientation
|
|
BoxLayout:
|
|
orientation: 'horizontal'
|
|
size_hint_y: None
|
|
height: dp(40)
|
|
spacing: dp(10)
|
|
|
|
Label:
|
|
text: 'Orientation:'
|
|
size_hint_x: 0.3
|
|
text_size: self.size
|
|
halign: 'left'
|
|
valign: 'middle'
|
|
|
|
TextInput:
|
|
id: orientation_input
|
|
size_hint_x: 0.7
|
|
multiline: False
|
|
font_size: sp(14)
|
|
write_tab: False
|
|
on_touch_down: root.on_input_touch(self, args[1]) if self.collide_point(*args[1].pos) else None
|
|
|
|
# Touch
|
|
BoxLayout:
|
|
orientation: 'horizontal'
|
|
size_hint_y: None
|
|
height: dp(40)
|
|
spacing: dp(10)
|
|
|
|
Label:
|
|
text: 'Touch:'
|
|
size_hint_x: 0.3
|
|
text_size: self.size
|
|
halign: 'left'
|
|
valign: 'middle'
|
|
|
|
TextInput:
|
|
id: touch_input
|
|
size_hint_x: 0.7
|
|
multiline: False
|
|
font_size: sp(14)
|
|
write_tab: False
|
|
on_touch_down: root.on_input_touch(self, args[1]) if self.collide_point(*args[1].pos) else None
|
|
|
|
# Resolution
|
|
BoxLayout:
|
|
orientation: 'horizontal'
|
|
size_hint_y: None
|
|
height: dp(40)
|
|
spacing: dp(10)
|
|
|
|
Label:
|
|
text: 'Max Resolution:'
|
|
size_hint_x: 0.3
|
|
text_size: self.size
|
|
halign: 'left'
|
|
valign: 'middle'
|
|
|
|
TextInput:
|
|
id: resolution_input
|
|
size_hint_x: 0.7
|
|
multiline: False
|
|
font_size: sp(14)
|
|
hint_text: '1920x1080 or auto'
|
|
write_tab: False
|
|
on_touch_down: root.on_input_touch(self, args[1]) if self.collide_point(*args[1].pos) else None
|
|
|
|
Widget:
|
|
size_hint_y: 0.05
|
|
|
|
# Test Connection Button
|
|
Button:
|
|
id: test_connection_btn
|
|
text: 'Test Server Connection'
|
|
size_hint_y: None
|
|
height: dp(50)
|
|
background_color: 0.2, 0.4, 0.8, 1
|
|
on_press: root.test_connection()
|
|
|
|
# Connection Status Label
|
|
Label:
|
|
id: connection_status
|
|
text: 'Click button to test connection'
|
|
size_hint_y: None
|
|
height: dp(40)
|
|
text_size: self.size
|
|
halign: 'center'
|
|
valign: 'middle'
|
|
color: 0.7, 0.7, 0.7, 1
|
|
|
|
Widget:
|
|
size_hint_y: 0.05
|
|
|
|
# Status information
|
|
Label:
|
|
id: playlist_info
|
|
text: 'Playlist Version: N/A'
|
|
size_hint_y: None
|
|
height: dp(30)
|
|
text_size: self.size
|
|
halign: 'left'
|
|
valign: 'middle'
|
|
|
|
Label:
|
|
id: media_count_info
|
|
text: 'Media Count: 0'
|
|
size_hint_y: None
|
|
height: dp(30)
|
|
text_size: self.size
|
|
halign: 'left'
|
|
valign: 'middle'
|
|
|
|
Label:
|
|
id: status_info
|
|
text: 'Status: Idle'
|
|
size_hint_y: None
|
|
height: dp(30)
|
|
text_size: self.size
|
|
halign: 'left'
|
|
valign: 'middle'
|
|
|
|
Widget:
|
|
size_hint_y: 0.2
|
|
|
|
# Action buttons
|
|
BoxLayout:
|
|
orientation: 'horizontal'
|
|
size_hint_y: None
|
|
height: dp(50)
|
|
spacing: dp(20)
|
|
|
|
Button:
|
|
text: 'Save & Close'
|
|
background_color: 0.2, 0.6, 0.2, 1
|
|
on_press: root.save_and_close()
|
|
|
|
Button:
|
|
text: 'Cancel'
|
|
background_color: 0.6, 0.2, 0.2, 1
|
|
on_press: root.dismiss() |