Repository: mahart-studio/kivystudio
Branch: master
Commit: c97c38f6bf62
Files: 87
Total size: 268.2 KB
Directory structure:
gitextract_7y9q4d6t/
├── .gitignore
├── LICENSE
├── README.md
├── kivystudio/
│ ├── __init__.py
│ ├── assembler.py
│ ├── behaviors/
│ │ ├── __init__.py
│ │ ├── highlightbehavior.py
│ │ ├── hoverbehavior.py
│ │ └── hoverinfobehavior.py
│ ├── components/
│ │ ├── __init__.py
│ │ ├── codeplace/
│ │ │ ├── __init__.py
│ │ │ ├── code.kv
│ │ │ ├── codeplace.py
│ │ │ └── tabs/
│ │ │ ├── __init__.py
│ │ │ ├── codetab.py
│ │ │ ├── errortab.py
│ │ │ ├── welcome.kv
│ │ │ └── welcometab.py
│ │ ├── emulator_area/
│ │ │ ├── __init__.py
│ │ │ ├── emulator.kv
│ │ │ └── screen_drop.py
│ │ ├── screens/
│ │ │ ├── __init__.py
│ │ │ ├── screen.kv
│ │ │ └── screen_test.py
│ │ ├── sibebar/
│ │ │ ├── __init__.py
│ │ │ ├── fileexplorer/
│ │ │ │ ├── __init__.py
│ │ │ │ └── filewidgets.py
│ │ │ ├── generalsearch.py
│ │ │ ├── gitmanager.py
│ │ │ └── sidebar.kv
│ │ ├── terminal/
│ │ │ ├── __init__.py
│ │ │ ├── command_terminal.py
│ │ │ └── logger_space.py
│ │ └── topmenu/
│ │ ├── __init__.py
│ │ ├── dropmenu.kv
│ │ └── dropmenu.py
│ ├── libs/
│ │ ├── __init__.py
│ │ └── resizablebehavior/
│ │ ├── LICENSE
│ │ ├── README.md
│ │ ├── __init__.py
│ │ ├── modal_cursor.py
│ │ └── resize.py
│ ├── main.kv
│ ├── main.py
│ ├── parser/
│ │ └── __init__.py
│ ├── resources/
│ │ └── font-awesome.fontd
│ ├── settings.py
│ ├── tools/
│ │ ├── __init__.py
│ │ ├── iconfonts/
│ │ │ ├── LICENSE
│ │ │ ├── README.md
│ │ │ ├── __init__.py
│ │ │ ├── iconfonts.py
│ │ │ └── test/
│ │ │ ├── font-awesome.css
│ │ │ ├── font-awesome.fontd
│ │ │ └── main.py
│ │ ├── infolabel.py
│ │ ├── logger.py
│ │ └── quicktools.py
│ └── widgets/
│ ├── __init__.py
│ ├── codeinput/
│ │ ├── __init__.py
│ │ ├── code_extra_behavior.py
│ │ ├── code_find.py
│ │ ├── codeinput.kv
│ │ ├── codeinput.py
│ │ ├── styles/
│ │ │ ├── __init__.py
│ │ │ └── native_tweak.py
│ │ └── tools.py
│ ├── dropdown.py
│ ├── filemanager/
│ │ ├── LICENSE
│ │ ├── README.md
│ │ ├── __init__.py
│ │ ├── filechooserthumbview/
│ │ │ ├── LICENSE
│ │ │ └── __init__.py
│ │ └── filemanager.kv
│ ├── iconlabel.py
│ ├── rightclick_drop.py
│ ├── searchinput.py
│ ├── splitter.py
│ └── tabbedpanel.py
├── project/
│ ├── prjtTest1.htm
│ └── prjtTest1.js
├── repoTest1.js
├── repoTest2.htm
├── setup.py
├── tests/
│ ├── test_codeplace.py
│ └── test_emulator.py
└── to-do.txt
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
*.pyc
*.zip
*.gif
pyc.py
__pycache__/
.vscode/
build/
dist/
*egg-info
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2019 @mahartstudio
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
#### KivyStudio
[](https://travis-ci.com/MichaelStott/KivMob)
[](https://www.python.org/downloads/release/python-270/)
[](https://www.python.org/downloads/release/python-270/)
[](https://pepy.tech/project/kivmob)
[](https://codeclimate.com/github/MichaelStott/KivMob/maintainability)
A kivy software development environment targeted towards fast testing and interactive development.
* #### Features
- Emulation can be done in real time
- Supports multiple screen views for mobile devices
- Supports orientation changes for mobile devices
- Supports outer window emulation for desktop intended emulation and for full test on mobile devices
* **Status**: under development...
* **Release**: 0
### Installation
Package file for various platform will be available on first release
### Demo Screenshot
### Quickstart
* Create a new folder
* Open **kivystudio**.
* On the top menu bar go to **File** option
* Click **Open Folder**,
* Choose your folder
* Press Ctrl + N : a new file will open-up
Copy the following code into the editor provided.
```python
from kivy.app import App
from kivy.uix.button import Button
class MyApp(App):
def build(self):
return Button(text='Welcome to KivyStudio!!')
if __name__ == '__main__':
MyApp().run()
```
* To save the code, press Ctrl + S.
* Right click on **File** tab
* Choose **Set for Emulation**, or press Ctrl + E to select the file for emulation
* Then, press Ctrl + R to see the output, *OR* you can also set auto-emulation from **File** tab
* To switch screens, use the Ctrl + Tab combination
* To open and close terminal panel, press Ctrl + `
### Contributions
To contribute to this project
* Fork the repository
* Clone it
``` git clone https://github.com/mahart-studio/kivystudio.git```
* Start by solving an issue or fixing a known bug
* Create a **Pull Request**
* PR would be merged after review.
================================================
FILE: kivystudio/__init__.py
================================================
from kivy.config import Config
Config.set('graphics', 'width', '1000')
Config.set('graphics', 'height', '730')
Config.set('kivy', 'exit_on_escape', '0')
Config.set('input', 'mouse', 'mouse,disable_multitouch')
__version__ = '0.2.0.dev'
def get_kivystudio_app():
from .main import studio_app
return studio_app
================================================
FILE: kivystudio/assembler.py
================================================
from kivy.uix.boxlayout import BoxLayout
from kivy.core.window import Window
from kivy.clock import mainthread
from kivystudio.widgets.filemanager import filemanager
from kivystudio.parser import emulate_file
from kivystudio.components.topmenu import TopMenu
from kivystudio.components.codeplace import code_place, code_container
from kivystudio.components.sibebar import SideBar
from kivystudio.components.terminal import TerminalSpace
from kivystudio.components.emulator_area import get_emulator_area
from kivystudio.settings import settings_obj
class Assembly(BoxLayout):
'''
Widget to assemble and structure
all widgets
'''
def add_new_tab(paths):
for path in paths:
code_place.add_code_tab(filename=path)
@mainthread
def open_project(paths):
if paths:
side_bar.ids.explorer_btn='down'
side_bar.fileexplorer.load_directory(paths[0])
def main_key_handler(win, *args):
'''' main keyboard and shortcut lisener '''
# print(args)
if args[0] == 114 and 'ctrl' in args[3]: # emulate file Ctrl+R
emulate_file(emulator_area.emulation_file)
elif args[0] == 107 and 'ctrl' in args[3]: # Open folder Ctrl+K
filemanager.choose_dir(path='.',on_selection=open_project)
elif args[0] == 111 and 'ctrl' in args[3]: # Open file Ctrl+O
filemanager.open_file(path='.',on_selection=add_new_tab)
elif args[0] == 110 and 'ctrl' in args[3]: # New file Ctrl+N
code_place.add_code_tab(tab_type='new_file')
elif args[0] == 96 and 'ctrl' in args[3]: # Toggle terminal Ctrl+`
from kivystudio.components.codeplace import terminal
terminal.toggle_state()
elif args[0] == 45 and 'ctrl' in args[3]: # Zoom out Ctrl -
if settings_obj.dpi_scale > 0.8:
settings_obj.dpi_scale -= 0.1
elif args[0] == 61 and 'ctrl' in args[3]: # Zoom in Ctrl +
if settings_obj.dpi_scale < 1.4:
settings_obj.dpi_scale += 0.1
Window.bind(on_key_down=main_key_handler)
emulator_area = get_emulator_area()
side_bar = SideBar()
Assembler = Assembly()
box = Assembler.ids.box
box.add_widget(side_bar)
box.add_widget(code_container)
box.add_widget(emulator_area)
================================================
FILE: kivystudio/behaviors/__init__.py
================================================
from .highlightbehavior import HighlightBehavior
from .hoverbehavior import HoverBehavior
from .hoverinfobehavior import HoverInfoBehavior
================================================
FILE: kivystudio/behaviors/highlightbehavior.py
================================================
from kivy.uix.gridlayout import GridLayout
from kivy.uix.button import Button
from kivy.properties import (BooleanProperty, ObjectProperty,
ListProperty, OptionProperty, NumericProperty)
from kivy.base import runTouchApp
from kivy.graphics import InstructionGroup, Color, Rectangle, RoundedRectangle, Callback
from kivy.uix.behaviors import FocusBehavior
from kivy.clock import Clock
from kivy.event import EventDispatcher
from kivy.core.window import Window, Keyboard
__all__ = ('HighlightBehavior', )
class HighlightBehavior(object):
current_highlighted_child = ObjectProperty(None, allownone=True)
''' current highlighted child
on ObjectProperty and defualts to None '''
highlighted_color = ListProperty([.2,.5,1,.5])
''' color that show highlighted widget
a ListProperty defualts to [.2,.5,1.5] make sure it transparent
because it's drawn over the widget '''
auto_scroll_to = BooleanProperty(False)
''' automatically scroll to the widget when widget is out of focus
note* parent most be a scrollview to enable the property'''
highlighted_shape = OptionProperty('rectangle', options=['rounded_rectangle','rectangle'])
highlight_orientation = OptionProperty('vertical', options=['vertical',
'horizontal', 'grid'])
''' Orientation in which the highlighting will take place if
grid grid len must be set'''
instruction_canvas = ObjectProperty(InstructionGroup())
''' internal instruction group used to draw the canvas on the
currently highlighted child '''
grid_len = NumericProperty(0)
def __init__(self, **kwargs):
super(HighlightBehavior, self).__init__(**kwargs)
def set_first_child(self, dt):
if len(self.children) >= 1:
self.set_highlighted(self.children[0])
def on_highlighted_color(self, *args):
self.redraw_canvas()
def redraw_canvas(self, *args):
if self.current_highlighted_child:
self.instruction_canvas.clear()
self.instruction_canvas.add(Color(*self.highlighted_color))
if self.highlighted_shape =='rectangle':
self.instruction_canvas.add(Rectangle(pos=self.current_highlighted_child.pos, size=self.current_highlighted_child.size))
elif self.highlighted_shape =='rounded_rectangle':
self.instruction_canvas.add(RoundedRectangle(pos=self.current_highlighted_child.pos, size=self.current_highlighted_child.size))
else:
raise Exception('Invalid highlighted shape {}'.format(self.highlighted_shape))
def on_focus(self, arg, focus):
if focus:
Window.bind(on_key_down=self.handle_key)
if self.current_highlighted_child is None:
Clock.schedule_once(self.set_first_child)
else:
Window.unbind(on_key_down=self.handle_key)
def on_children(self, *args):
if len(self.children) == 1:
self.set_highlighted(self.children[0])
def set_highlighted(self, child):
if not (child == self.current_highlighted_child):
if self.current_highlighted_child: # remove the canvas from the previosly highlighted child
self.current_highlighted_child.canvas.after.remove(self.instruction_canvas)
child.canvas.after.add(self.instruction_canvas)
self.current_highlighted_child = child
with child.canvas:
Callback(self.redraw_canvas)
def handle_key(self, keyboard, key, codepoint, text, modifier, *args):
key_str = Keyboard.keycode_to_string(Window._system_keyboard, key)
modifier.sort()
if modifier:
value = '_'.join(modifier) + '_' + key_str
else:
value = key_str
callable_method = 'do_' + value
try:
func = getattr(self, callable_method)
except AttributeError:
pass
else:
func()
def _moving(self):
if self.auto_scroll_to:
self.parent.scroll_to(self.current_highlighted_child)
def do_up(self):
if self.highlight_orientation == 'vertical':
children = self.children[:]
index = children.index(self.current_highlighted_child)
if not(index >= len(children)-1):
self.set_highlighted(children[(index)+1])
self._moving()
elif self.highlight_orientation == 'horizontal':
pass
elif self.highlight_orientation == 'grid':
children = self.children[:]
index = children.index(self.current_highlighted_child)
if not(index+self.grid_len >= len(children)):
self.set_highlighted(children[(index)+self.grid_len])
self._moving()
else:
raise Exception('invalid highlight_orientation %s'%self.highlight_orientation)
def do_down(self):
if self.highlight_orientation == 'vertical':
children = self.children[:]
index = children.index(self.current_highlighted_child)
if not(index < 1):
self.set_highlighted(children[(index)-1])
self._moving()
elif self.highlight_orientation == 'horizontal':
pass
elif self.highlight_orientation == 'grid':
children = self.children[:]
index = children.index(self.current_highlighted_child)
if not(index+self.grid_len < 1):
self.set_highlighted(children[(index)-self.grid_len])
self._moving()
else:
raise Exception('invalid highlight_orientation %s'%self.highlight_orientation)
def do_right(self):
if self.highlight_orientation == 'vertical':
pass
elif self.highlight_orientation == 'horizontal' or self.highlight_orientation == 'grid':
self._moving()
children = self.children[:]
index = children.index(self.current_highlighted_child)
if not(index < 1):
self.set_highlighted(children[(index)-1])
self._moving()
else:
raise Exception('invalid highlight_orientation %s'%self.highlight_orientation)
def do_left(self):
if self.highlight_orientation == 'vertical':
pass
elif self.highlight_orientation == 'horizontal' or self.highlight_orientation == 'grid':
children = self.children[:]
index = children.index(self.current_highlighted_child)
if not(index >= len(children)-1):
self.set_highlighted(children[(index)+1])
self._moving()
else:
raise Exception('invalid highlight_orientation %s'%self.highlight_orientation)
def do_enter(self):
pass
def do_ctrl_up(self):
pass
def do_shift_up(self):
pass
def do_shift_down(self):
pass
def do_shift_left(self):
pass
def do_shift_up(self):
pass
'''
if value :
self.map.get(value)[0]()
return True
'''
class What(HighlightBehavior, FocusBehavior, GridLayout):
pass
if __name__ == "__main__":
what = What(cols=3, grid_len=3, highlight_orientation='grid')
for btn in range(12):
what.add_widget(Button(text=str(btn)))
runTouchApp(what)
================================================
FILE: kivystudio/behaviors/hoverbehavior.py
================================================
''' A quick implementation of mouse hovering
just fires one event called 'on_hover'
when the mouse hover on a widget the inhenrites from this
behavior
Ex:
class HoverButton(HoverBehavior, Button):
def on_hover(self, *args):
if self.hover:
self.text = 'Yeah!!'
else:
self.text =''
'''
from kivy.core.window import Window
from kivy.properties import ObjectProperty
class HoverBehavior(object):
hover = ObjectProperty(False)
''' indicate is mouse if over the widget
defaults to False'''
def on_parent(self, *args):
if self.parent:
Window.bind(mouse_pos=self.on_mouse_move)
else:
Window.unbind(mouse_pos=self.on_mouse_move)
def on_mouse_move(self, win, pos):
if self.collide_point(*self.to_widget(*pos)):
self.hover = True
else:
self.hover = False
if __name__ == "__main__":
from kivy.base import runTouchApp as app
from kivy.uix.button import Button
from kivy.uix.boxlayout import BoxLayout
class HoverButton(HoverBehavior, Button):
def on_hover(self, *args):
if self.is_hover:
self.text = 'Yeah!!'
else:
self.text =''
box=BoxLayout()
box.add_widget(HoverButton())
box.add_widget(Button(text='Hello'))
app(box)
================================================
FILE: kivystudio/behaviors/hoverinfobehavior.py
================================================
'''
Mixin behavior widget that inherits from kivystudio.behavior.HoverBehavior
used on a widget, to show extra infomation on what the widgets does
by hovering on it
Simple usages:
class MyButton(HoverInfoBehavior, Button):
def __init__(self, **kwargs):
super(MyButton, self).__init__(**kwargs)
self.info_text = 'Click me'
# or if the info text might change you, could bind it to an atrr
self.mytext = 'somthing'
self.info_text_attr = 'mytext'
'''
from kivy.clock import Clock
from kivy.properties import StringProperty
from kivystudio.tools import infolabel
from .hoverbehavior import HoverBehavior
class HoverInfoBehavior(HoverBehavior):
info_text = StringProperty('')
info_text_attr = StringProperty('')
def on_parent(self, *a):
if self.parent is None:
infolabel.remove_info_on_mouse()
return super(HoverInfoBehavior, self).on_parent(*a)
def on_hover(self, *a):
if self.hover:
Clock.schedule_once(self.show_label_info,1)
else:
Clock.unschedule(self.show_label_info)
infolabel.remove_info_on_mouse()
def show_label_info(self,dt):
info_text = self.info_text
if self.info_text_attr:
info_text = getattr(self, self.info_text_attr)
# if info_text:
infolabel.show_info_on_mouse(info_text)
================================================
FILE: kivystudio/components/__init__.py
================================================
================================================
FILE: kivystudio/components/codeplace/__init__.py
================================================
from .codeplace import CodePlace
from kivystudio.widgets.splitter import StudioSplitter
from kivystudio.components.terminal import TerminalSpace
class CodeContainer(StudioSplitter):
pass
code_container = CodeContainer()
container = code_container.ids.container
code_place = CodePlace()
code_place.add_code_tab(tab_type='welcome') # add welcoming tab
terminal = TerminalSpace()
container.add_widget(code_place)
container.add_widget(terminal)
================================================
FILE: kivystudio/components/codeplace/code.kv
================================================
:
sizable_from: 'right'
max_size: root.parent.width-self.parent.children[2].width if self.parent and len(self.parent.children)>2 else 0
min_size: '700dp'
FloatLayout:
id: container
:
orientation: 'vertical'
tab_manager: tab_manager
pos_hint: {'center_y': .5, 'center_x': .5}
CodeTabsContainer:
size_hint_y: None
height: '36dp'
canvas.before:
Color:
rgba: (0.12, 0.12, 0.12, 1)
Rectangle:
size: self.size
pos: self.pos
# canvas to show shadow division
canvas.after:
Color:
rgba: (0, 0, 0, .4)
Line:
points: [self.x,self.y, self.right,self.y]
Color:
rgba: (0, 0, 0, .3)
Line:
points: [self.x,self.y-1, self.x-1,self.y-1]
Color:
rgba: (0, 0, 0, .2)
Line:
points: [self.x,self.y-2, self.x,self.y-2]
Color:
rgba: (0, 0, 0, .1)
Line:
points: [self.x,self.y-3, self.x,self.y-3]
width: 2
GridLayout:
rows: 1
id: tab_manager
size_hint_x: None
width: self.minimum_width
:
size_hint_x: None
width: self.minimum_width
padding: '6dp'
spacing: '3dp'
canvas_color: (0.12, 0.12, 0.12, 1)
allow_no_selection: False
group: '__tabed_btn__'
on_state:
if self.state == 'down': self.canvas_color= (.2,.2,.2,1)
else: self.canvas_color= (0.12, 0.12, 0.12, 1)
canvas.before:
Color:
rgba: self.canvas_color
Rectangle:
size: self.size
pos: self.pos
IconLabel:
size_hint_x: None
width: '10dp'
text: '%s'%(icon('fa-file-code-o'))
color: .2,.5,1,.8
Label:
font_size: '13.5dp'
text: root.text
text_size: None, None
padding: '3dp', 0
shorten: True
shorten_from: 'right'
size_hint_x: None
width: max(self.texture_size[0]+10, 110)
TabPannelIndicator:
id: indicator
on_release: root.close_tab()
:
size_hint_x: None
width: '12dp'
:
MenuButton:
on_release: root.close_tab()
MenuLabel:
text: 'Close'
MenuLabel:
text: 'Ctrl+W'
type: 'shortcut'
ToggleMenuButton:
text: 'Set file for emulation'
on_state:
if self.state=='down': root.set_for_emulation()
else: root.set_for_emulation(remove=True)
MenuButton:
MenuLabel:
text: 'Copy path'
MenuLabel:
text: 'Alt-Ctrl+C'
type: 'shortcut'
MenuButton:
MenuLabel:
text: 'Copy relative path'
MenuLabel:
text: 'Alt-Ctrl+Shift+C'
type: 'shortcut'
MenuLabel:
text: 'Split Up'
MenuButton:
MenuLabel:
text: 'Split Down'
MenuButton:
MenuLabel:
text: 'Split Left'
MenuButton:
MenuLabel:
text: 'Split Right'
:
text: "This file is not displayed because it is either binary or uses an unsupported text encoding."
canvas.before:
Color:
rgba: (0.12, 0.12, 0.12, 1)
Rectangle:
size: self.size
pos: self.pos
================================================
FILE: kivystudio/components/codeplace/codeplace.py
================================================
from kivy.uix.screenmanager import ScreenManager, Screen, NoTransition, ScreenManagerException
from kivy.uix.scrollview import ScrollView
from kivy.uix.behaviors import ToggleButtonBehavior, FocusBehavior
from kivy.uix.boxlayout import BoxLayout
from kivy import properties as prop
from kivy.clock import Clock
from kivy.core.window import Window
from kivy.lang import Builder
from kivy.extras.highlight import KivyLexer
from kivystudio.widgets.codeinput import FullCodeInput
from kivystudio.widgets.filemanager import filemanager
from kivystudio.components.emulator_area import get_emulator_area
from kivystudio.tools import quicktools, load_kv
from kivystudio.tools.logger import Logger
from .tabs.welcometab import WelcomeTab
from .tabs.codetab import TabToggleButton
from .tabs.errortab import FileErrorTab
from pygments import lexers
import os
# file_formats = ['.py', '.kv', '.spec', '.txt']
def get_tab_from_group(filename):
all_tabs = ToggleButtonBehavior.get_widgets('__tabed_btn__')
if not all_tabs: return
for tab in all_tabs:
if tab.filename == filename:
return tab
def get_lexer_for_file(filename):
ext = os.path.splitext(filename)[1]
try:
lexer = lexers.get_lexer_for_filename(filename)
except lexers.ClassNotFound:
if ext == '.kv':
lexer = KivyLexer()
else:
lexer = lexers.TextLexer()
# print('found {} for {}'.format(lexer, filename))
return lexer
class CodeScreenManager(ScreenManager):
def __init__(self, **kwargs):
super(CodeScreenManager, self).__init__(**kwargs)
self.transition = NoTransition()
def add_widget(self, widget, name, tab_type='code'):
if tab_type=='code':
Clock.schedule_once(lambda dt: self.open_file(widget),1) # open the file
try:
widget.code_input.lexer = get_lexer_for_file(widget.filename)
except:
pass
screen = CodeScreen(name=name)
screen.add_widget(widget,tab_type=tab_type)
super(CodeScreenManager, self).add_widget(screen)
def open_file(self, code_input):
if os.path.exists(code_input.filename):
with open(code_input.filename, 'r') as f:
code_input.code_input.focus = False
code_input.code_input.text = f.read()
def save_current_tab(self):
self.get_screen(self.current).save_file()
def save_all_tabs(self):
for name in self.screen_names:
self.get_screen(name).save_file()
def get_children_with_filename(self, filename):
try:
child = list(filter(lambda child: child.name==filename, self.screens))[0]
return child
except IndexError:
raise Exception('code manager as no child with such filename {}'.format(filename))
class CodeScreen(Screen):
code_field = prop.ObjectProperty(None)
def on_pre_enter(self):
if self.code_field:
self.code_field.code_input.focus = True
Window.bind(on_key_down=self.keyboard_down)
tab = get_tab_from_group(self.name)
if tab:
Clock.schedule_once(lambda dt: setattr(tab, 'state', 'down'))
def on_pre_leave(self):
if self.code_field:
Window.unbind(on_key_down=self.keyboard_down)
def on_enter(self):
if self.code_field:
self.code_field.code_input.focus = True
def save_file(self, new_file=False, auto_save=False):
if self.code_field.tab_type=='code':
if not self.code_field.saved or new_file:
with open(self.name, 'w') as fn:
fn.write(self.code_field.code_input.text)
self.code_field.saved = True
if new_file:
self.code_field.code_input.lexer = get_lexer_for_file(self.name)
if self.code_field.tab_type=='new_file' and not auto_save:
filemanager.save_file(path='/root', on_selection=self.save_new_file)
def save_new_file(self, paths):
if paths:
path=paths[0]
self.code_field.tab.filename=path
self.code_field.tab.text = os.path.split(path)[1]
self.code_field.tab_type='code'
self.name=path
self.save_file(new_file=True)
def add_widget(self, widget, tab_type='code'):
super(CodeScreen, self).add_widget(widget)
if tab_type=='code' or tab_type=='new_file':
self.code_field = widget
self.code_field.tab_type=tab_type
self.code_field.bind(saved=self.saving_file)
def saving_file(self, ins, saved):
tab = get_tab_from_group(self.name)
tab.saved = saved
def keyboard_down(self, window, *args):
# print(args)
if args[0] == 115 and 'ctrl' in args[3]: # save file Ctrl+S
self.save_file()
return False
if args[0] == 101 and 'ctrl' in args[3]: # select file for emulation file Ctrl+E
if os.path.exists(self.name):
get_emulator_area().emulation_file=self.name
Logger.info("Emulator: File '{}' selected".format(self.name))
else:
Logger.info("Emulator: invalid file selected".format(self.name))
class CodePlace(BoxLayout):
code_manager = prop.ObjectProperty(None)
tab_manager = prop.ObjectProperty(None)
new_empty_tab = prop.NumericProperty(0)
'''count of empty tabs that has been opened
'''
def __init__(self, **kwargs):
super(CodePlace, self).__init__(**kwargs)
self.code_manager = CodeScreenManager()
self.add_widget(self.code_manager)
Window.bind(on_key_down=self.keyboard_down)
Window.bind(on_dropfile=self.file_droped)
def file_droped(self, window, filename, *args):
if self.collide_point(*window.mouse_pos):
print('File droped on code input {} '.format(filename))
if filename:
self.add_code_tab(filename=filename.decode('utf-8'))
def add_widget(self, widget, tab_type=''):
if len(self.children) > 1:
if tab_type =='code' or tab_type =='new_file' or tab_type=='unsupported':
tab = TabToggleButton(text=os.path.split(widget.filename)[1],
filename=widget.filename)
widget.tab = tab
widget.tab_type = tab_type
self.code_manager.add_widget(widget, widget.filename, tab_type=tab_type)
elif tab_type=='welcome':
self.code_manager.add_widget(widget, 'kivystudiowelcome', tab_type=tab_type)
tab = TabToggleButton(text='Welcome',filename='kivystudiowelcome')
tab.bind(state=self.change_screen)
self.tab_manager.add_widget(tab)
Clock.schedule_once(lambda dt: setattr(tab, 'state', 'down'))
else:
super(CodePlace, self).add_widget(widget)
def change_screen(self, tab, state):
if state == 'down':
self.code_manager.current = tab.filename
checked_list = list(filter(lambda child: child != tab, ToggleButtonBehavior.get_widgets(tab.group)))
for child in checked_list:
if child != tab:
child.state='normal'
def keyboard_down(self, window, *args):
if args[0] == 9 and args[3] == ['ctrl']: # switching screen with ctrl tab
self.code_manager.current = self.code_manager.next()
return True
if args[0] == 119 and args[3] == ['ctrl']: # close tab
try:
name = self.code_manager.get_screen(self.code_manager.current).name
tab = get_tab_from_group(name)
self.remove_code_tab(tab)
except ScreenManagerException:
pass
def remove_code_tab(self, tab):
self.tab_manager.remove_widget(tab)
codeinput=self.code_manager.get_children_with_filename(tab.filename)
self.code_manager.remove_widget(codeinput)
if tab.filename.startswith('Untitled-') and not os.path.exists(tab.filename):
self.new_empty_tab -= 1
def add_code_tab(self, filename='', tab_type='code'):
if filename and os.path.exists(filename):
if not quicktools.is_binary(filename):
widget=FullCodeInput(filename=filename)
else:
widget=FileErrorTab(filename=filename)
tab_type='unsupported'
try:
self.code_manager.get_screen(filename)
self.code_manager.current=filename
except ScreenManagerException: # then it is not added
self.add_widget(widget, tab_type=tab_type)
elif tab_type=='new_file': # a new tab
self.new_empty_tab += 1
while True:
try:
self.code_manager.get_screen(filename)
self.code_manager.current=filename
except ScreenManagerException: # then it is not added
filename = 'Untitled-{}'.format(self.new_empty_tab)
self.add_widget(FullCodeInput(filename=filename), tab_type=tab_type)
return
self.new_empty_tab += 1
elif tab_type == 'welcome':
try:
self.code_manager.get_screen('kivystudiowelcome')
self.code_manager.current=filename
except ScreenManagerException: # then it is not added
self.add_widget(WelcomeTab(), tab_type=tab_type)
class CodeTabsContainer(ScrollView):
'''horizontal scrollview where the small tab
buttons lay'''
def on_touch_down(self, touch):
FocusBehavior.ignored_touch.append(touch)
return super(CodeTabsContainer, self).on_touch_down(touch)
load_kv(__file__, 'code.kv')
================================================
FILE: kivystudio/components/codeplace/tabs/__init__.py
================================================
================================================
FILE: kivystudio/components/codeplace/tabs/codetab.py
================================================
from kivy.uix.behaviors import ToggleButtonBehavior,ButtonBehavior
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.behaviors import FocusBehavior
from kivy.uix.image import Image
from kivy.core.window import Window
from kivy.clock import Clock, mainthread
from kivy.properties import (StringProperty,
ObjectProperty,
BooleanProperty)
from kivystudio.behaviors import HoverInfoBehavior
from kivystudio.behaviors import HighlightBehavior
from kivystudio.widgets.iconlabel import IconLabelButton
from kivystudio.widgets.rightclick_drop import RightClickDrop
from kivystudio.tools import set_auto_mouse_position
from kivystudio.tools.iconfonts import icon
from kivystudio.components.emulator_area import get_emulator_area
rightclick_dropdown = [None]
class TabToggleButton(HoverInfoBehavior, ToggleButtonBehavior, BoxLayout):
filename = StringProperty('')
saved = BooleanProperty(True)
text = StringProperty('')
def __init__(self, **kwargs):
super(TabToggleButton, self).__init__(**kwargs)
# set the info attr because filename could change
self.info_text_attr = 'filename'
if rightclick_dropdown[0] is None:
self.rightclick_dropdown = CodeTabDropDown()
rightclick_dropdown[0] = self.rightclick_dropdown
else:
self.rightclick_dropdown=rightclick_dropdown[0]
def on_saved(self, *args):
if self.saved:
self.ids.indicator.text =''
elif not self.saved:
self.ids.indicator.text = '%s' % (icon('fa-circle'))
self.ids.indicator.font_size = '12dp'
def on_state(self, *args):
if self.state=='down' and not self.saved:
self.ids.indicator.text = '%s' % (icon('fa-circle'))
self.ids.indicator.font_size = '12dp'
elif self.state=='down' and self.saved:
self.ids.indicator.text = '%s' % (icon('fa-close'))
self.ids.indicator.font_size = '16dp'
elif self.state=='normal':
self.ids.indicator.source =''
def on_touch_down(self,touch):
if self.collide_point(*touch.pos):
if touch.button == 'right':
self.rightclick_dropdown.open(self)
FocusBehavior.ignored_touch.append(touch)
return True
if touch.button == 'left':
return super(TabToggleButton, self).on_touch_down(touch)
def close_tab(self):
from kivystudio.assembler import code_place
code_place.remove_code_tab(self)
def __str__(self):
return self.filename
class CodeTabDropDown(RightClickDrop):
def __init__(self, **kwargs):
super(CodeTabDropDown, self).__init__(**kwargs)
self.tab = None
def on_touch_down(self, touch):
if self.collide_point(*touch.pos): # touch should not unfocus input
FocusBehavior.ignored_touch.append(touch)
return super(CodeTabDropDown,self).on_touch_down(touch)
def open(self, tab):
self.tab=tab
super(CodeTabDropDown, self).open()
def set_for_emulation(self, remove=False):
if not remove:
get_emulator_area().emulation_file=self.tab.filename
else:
get_emulator_area().emulation_file=''
def close_tab(self):
self.dismiss()
self.tab.close_tab()
class TabPannelIndicator(IconLabelButton):
def on_hover(self, *a):
if self.hover:
self.text = '%s' % (icon('fa-close'))
self.font_size = '16dp'
if not self.hover and not self.parent.saved:
self.text = '%s' % (icon('fa-circle'))
self.font_size = '12dp'
if not self.hover and self.parent.saved and self.parent.state=='normal':
self.text = ''
if not self.hover and self.parent.saved and self.parent.state=='down':
self.text = '%s' % (icon('fa-close'))
self.font_size = '16dp'
return super(TabPannelIndicator, self).on_hover(*a)
================================================
FILE: kivystudio/components/codeplace/tabs/errortab.py
================================================
from kivy.uix.label import Label
from kivy.properties import StringProperty
class FileErrorTab(Label):
filename = StringProperty('')
================================================
FILE: kivystudio/components/codeplace/tabs/welcome.kv
================================================
#: import icon kivystudio.tools.iconfonts.icon
#: import quicktools kivystudio.tools.quicktools
# IconLabel:
# text: '%s'%(icon('fa-bug'))
# IconLabel:
# text: '%s'%(icon('fa-code-fork'))
:
BoxLayout:
orientation: 'vertical'
BoxLayout:
size_hint_y: None
height:'80dp'
orientation: 'vertical'
Label:
haling:'center'
text: 'Kivy Studio'
bold: True
font_size: '40dp'
BoxLayout:
size_hint_y: None
height:'150dp'
GridLayout:
cols: 4
IconLabel:
text: '%s'%(icon('fa-android'))
color: .2,1,.2
font_size: '64dp'
IconLabel:
text: '%s'%(icon('fa-apple'))
color: .9,.9,.95
font_size: '64dp'
IconLabel:
text: '%s'%(icon('fa-linux'))
font_size: '64dp'
IconLabel:
text: '%s'%(icon('fa-windows'))
font_size: '64dp'
BoxLayout:
padding: '30dp'
GridLayout:
cols: 1
Label:
size_hint_y: None
height: '32dp'
text: 'Start'
bold: True
font_size: '18dp'
BoxLayout:
orientation: 'vertical'
size_hint_y: None
height: self.minimum_height
Label:
on_ref_press: quicktools.open_new_file()
text: '[u][color=2255FF][ref="d"]Open new file[/ref][/color][/u] - [ctrl N]'
markup: True
size_hint_y: None
height: '32dp'
Label:
on_ref_press: quicktools.open_file()
text: '[u][color=2255FF][ref="d"]Open file[/ref][/color][/u] - [ctrl O]'
markup: True
size_hint_y: None
height: '32dp'
Label:
on_ref_press: quicktools.open_folder()
text: '[u][color=2255FF][ref="d"]Open folder[/ref][/color][/u] - [ctrl K]'
markup: True
size_hint_y: None
height: '32dp'
GridLayout:
cols: 1
Label:
size_hint_y: None
height: '32dp'
text: 'Recent'
bold: True
font_size: '18dp'
BoxLayout:
orientation: 'vertical'
================================================
FILE: kivystudio/components/codeplace/tabs/welcometab.py
================================================
from kivystudio.widgets.iconlabel import IconLabel
from kivy.uix.boxlayout import BoxLayout
from kivy.lang import Builder
from os.path import join, dirname
Builder.load_file(join(dirname(__file__), 'welcome.kv'))
class WelcomeTab(BoxLayout):
pass
================================================
FILE: kivystudio/components/emulator_area/__init__.py
================================================
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.label import Label
from kivy.uix.behaviors import ToggleButtonBehavior
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.lang import Builder
from kivy.properties import ObjectProperty, StringProperty
from kivy.clock import Clock
from kivystudio.behaviors import HoverBehavior
from kivystudio.components import screens
from kivystudio.widgets.iconlabel import IconLabelButton
from .screen_drop import ScreenDrop
import os
__all__ = ('get_emulator',)
filepath = os.path.dirname(__file__)
Builder.load_file(os.path.join(filepath,'emulator.kv'))
class EmulatorArea(BoxLayout):
screen_display = ObjectProperty(None)
emulation_file = StringProperty('')
def __init__(self, **kwargs):
super(EmulatorArea, self).__init__(**kwargs)
self.screen_manager = EmulatorScreens()
self.add_widget(self.screen_manager)
self.screen_display = ScreenDisplay()
self.screen_manager.add_widget(self.screen_display)
def add_widget(self, widget):
super(EmulatorArea, self).add_widget(widget)
def toggle_orientation(self):
if self.screen_display.screen.orientation =='portrait':
self.screen_display.screen.orientation ='landscape'
else:
self.screen_display.screen.orientation ='portrait'
def open_screen_drop(self,widget):
ScreenDrop().open(widget, self.screen_display)
class ScreenTopMenu(BoxLayout):
screen = ObjectProperty(None)
class EmulatorScreens(ScreenManager):
def add_widget(self, widget):
screen = Screen()
screen.add_widget(widget)
super(EmulatorScreens, self).add_widget(screen)
class ScreenDisplay(HoverBehavior, FloatLayout):
screen = ObjectProperty(None)
topmenu = ObjectProperty(None)
screen_name = StringProperty('')
def __init__(self, **kwargs):
super(ScreenDisplay, self).__init__(**kwargs)
self.scaler = ScreenTopMenu()
self.screen_name = 'AndroidPhoneScreen'
def on_hover(self, *args):
pass
def on_screen_name(self, *a):
self.former_screen = self.screen
self.screen = getattr(screens, self.screen_name)()
def on_screen(self, obj, screen):
if self.former_screen:
root = self.former_screen.root_widget
if root:
self.former_screen.clear_widgets()
screen.add_widget(root)
# now add new screen
self.clear_widgets()
self.add_widget(screen)
self.bind(center=self.screen.setter('center'))
self.scaler.screen = screen
instance=[]
def get_emulator_area():
if instance:
return instance[0]
else:
emulator_area = EmulatorArea(size_hint_x=.45)
instance.append(emulator_area)
return emulator_area
================================================
FILE: kivystudio/components/emulator_area/emulator.kv
================================================
#: import split os.path.split
:
orientation: 'vertical'
GridLayout:
canvas.before:
Color:
rgba: (0.12, 0.12, 0.12, 1)
Rectangle:
size: self.size
pos: self.pos
rows: 1
id: tab_manager
size_hint_y: None
height: '36dp'
Label:
size_hint_x: None
width: min(self.texture_size[0]+dp(7), dp(108))
text: split(root.emulation_file)[1]
shorten: True
shorten_from: 'right'
IconLabelButton:
info_text: 'Zooom Out'
color: .8,.8,.8,1
size_hint_x: None
width: '36dp'
text: icon('fa-search-minus')
on_release:
if not root.screen_display.screen.scale < 0.10: root.screen_display.screen.scale -= 0.05
IconLabelButton:
info_text: 'Zooom In'
color: .8,.8,.8,1
text: icon('fa-search-plus')
size_hint_x: None
width: '36dp'
icon_source: 'images/scale2.png'
on_release:
if not root.screen_display.screen.scale > 1.0: root.screen_display.screen.scale += 0.05
IconToggleLabel:
info_text: 'Change Orientation'
color: .8,.8,.8,1
text: icon('fa-mobile', 24)
size_hint_x: None
width: '36dp'
angle:0
on_state:
root.toggle_orientation();
if self.state=='down': self.angle=90
else: self.angle=0
canvas:
Clear
PushMatrix
Rotate:
angle: self.angle
origin: self.center_x , self.center_y
Rectangle:
size: self.texture_size
pos: self.center_x - 12, self.center_y - 12
texture: self.texture
PopMatrix
IconLabelButton:
info_text: 'Change Screen'
on_release: root.open_screen_drop(self)
size_hint_x: None
width: '36dp'
text: icon('fa-ellipsis-h')
:
name: 'Emulator'
canvas.before:
Color:
rgba: .5,.5,.5,1
Rectangle:
size: self.size
pos: self.pos
:
auto_width: False
width: '260dp'
MenuButton:
on_release: root.screen_display.screen_name=self.children[0].text
MenuLabel:
text: 'IphoneScreen'
MenuButton:
on_release: root.screen_display.screen_name=self.children[0].text
MenuLabel:
text: 'IpadScreen'
MenuButton:
on_release: root.screen_display.screen_name=self.children[0].text
MenuLabel:
text: 'AndriodTabScreen'
MenuButton:
on_release: root.screen_display.screen_name=self.children[0].text
MenuLabel:
text: 'AndroidPhoneScreen'
================================================
FILE: kivystudio/components/emulator_area/screen_drop.py
================================================
from kivystudio.widgets.dropdown import DropDownBase
class ScreenDrop(DropDownBase):
def open(self, widget, screen_display):
self.screen_display = screen_display
super(ScreenDrop, self).open(widget)
================================================
FILE: kivystudio/components/screens/__init__.py
================================================
import os
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.properties import ObjectProperty, NumericProperty, StringProperty, ListProperty
from kivy.uix.scatter import Scatter
from kivy.lang import Builder
from kivy.metrics import dp
# module resources
from kivy.resources import resource_add_path
resource_add_path(os.path.dirname(os.path.realpath(__file__)))
__all__ = ('IphoneScreen', 'IpadScreen', 'AndroidPhoneScreen', 'AndriodTabScreen', )
class ScreenScatter(Scatter):
''' base widget for screens'''
root_widget = ObjectProperty(None, allow_none=True)
angle = NumericProperty(0)
orientation = StringProperty('portrait')
container = ObjectProperty(None)
border_size =ListProperty([0,0])
border_pos =ListProperty([0,0])
source = StringProperty('')
def __init__(self, **k):
super(ScreenScatter, self).__init__(**k)
self.bind(pos=self.set_border)
self.bind(size=self.set_border)
def add_widget(self, widget):
if len(self.children) > 0:
self.root_widget = widget
if widget.parent:
widget.parent.remove_widget(widget)
self.container.add_widget(widget)
else:
super(ScreenScatter, self).add_widget(widget)
def clear_widgets(self):
self.container.clear_widgets()
def on_orientation(self, *a):
if self.orientation == 'portrait':
self.angle = 0
self.container.size = (self.height,self.width)
elif self.orientation == 'landscape':
self.angle = -90
self.container.size = (self.height,self.width)
self.set_border(from_orientation=True)
def set_border(self, *a, **k):
if self.orientation == 'portrait':
self.border_pos = self.get_pos
self.border_size = self.get_size
elif self.orientation == 'landscape':
self.border_pos = self.get_pos
try:
from_orientation = k.pop('from_orientation')
except KeyError:
from_orientation = False
if from_orientation:
self.center = self.parent.center
def on_parent(self,*a):
if self.parent:
self.set_border()
self.center = self.parent.center
@property
def get_pos(self):
pass
@property
def get_size(self):
pass
class IphoneScreen(ScreenScatter):
@property
def get_pos(self):
pos = (-25, -dp(133))
if self.orientation == 'landscape':
return (-self.container.height+pos[0], pos[1])
return pos
@property
def get_size(self):
return (self.width + dp(50), self.height + dp(270))
class IpadScreen(ScreenScatter):
@property
def get_pos(self):
pos = (-dp(95), -dp(77))
if self.orientation == 'landscape':
return (-self.container.height+pos[0], pos[1])
return pos
@property
def get_size(self):
return (self.width + dp(190), self.height + dp(154))
class AndriodTabScreen(ScreenScatter):
@property
def get_pos(self):
if self.orientation == 'landscape':
return (-self.container.height-35, -dp(51))
return (-dp(35), -dp(51))
@property
def get_size(self):
return (self.width + dp(70), self.height + dp(102))
class AndroidPhoneScreen(ScreenScatter):
@property
def get_pos(self):
if self.orientation == 'landscape':
return (-self.container.height-16.5, -dp(85.5))
return (-dp(16.5), -dp(85.5))
@property
def get_size(self):
return (self.container.width + dp(32), self.container.height + dp(152))
class ScreenContainer(FloatLayout):
# overide
def add_widget(self,widget):
if len(self.children) > 1:
screen = self.ids.inner_container.get_screen('container')
screen.add_widget(widget)
# self.ids.inner_container.add_widget(screen)
else:
super(ScreenContainer,self).add_widget(widget)
# overide
def clear_widgets(self):
screen = self.ids.inner_container.get_screen('container')
screen.clear_widgets()
Builder.load_file('screen.kv')
if __name__ == "__main__":
from kivy.base import runTouchApp as app
app(build)
================================================
FILE: kivystudio/components/screens/screen.kv
================================================
#: import Window kivy.core.window.Window
# 398 804
:
size: container.size
container: container
scale: 0.75
ScreenContainer:
id: container
size: '320dp', '610dp'
source: 'images/android_lolipop1.png'
# 380 743
:
size: container.size
container: container
scale: 0.75
ScreenContainer:
id: container
size: ('300dp', '500dp')
source: 'images/iphone.png'
#1271 992
:
size: container.size
container: container
scale: 0.75
ScreenContainer:
id: container
size: ('800dp', '512dp')
source: 'images/ipad.png'
# 953 612
:
size: container.size
container: container
ScreenContainer:
id: container
size: ('500dp', '280dp')
source: 'images/android_tab.png'
my_parent: self.parent
:
do_rotation: False
do_scale: False
do_translation: False
# scale_min: 1
size_hint: None, None
auto_bring_to_front: False
:
source: ''
FloatLayout:
source: root.source
size_hint: None, None
pos_hint: {'center_y': .5, 'center_x': .5}
canvas.before:
PushMatrix:
Rotate:
angle: root.parent.angle if self.parent else 0
origin: (0,0,1)
BorderImage:
source: root.source
size: root.parent.border_size if self.parent else (0,0)
pos: root.parent.border_pos if self.parent else (0,0)
PopMatrix:
ScreenManager:
id: inner_container
Screen:
name: 'container'
================================================
FILE: kivystudio/components/screens/screen_test.py
================================================
from kivy.lang import Builder
from kivy.base import runTouchApp as app
from __init__ import *
app(
Builder.load_string('''
BoxLayout:
Carousel:
canvas.before:
Color:
rgba: 1,1,1,1
Rectangle:
size: self.size
pos: self.pos
FloatLayout:
IphoneScreen:
on_scale: self.center=root.center
center: root.center
id: screen
Button:
text: 'Hello'
font_size: '17dp'
BoxLayout:
pos_hint: {'y': .01, 'center_x': .5}
size_hint: None,None
size: '120dp', '60dp'
Button:
text: '-'
bold: True
on_release:
if not(screen.scale < -100.0) : screen.scale -= 0.05
Button:
text: '+'
bold: True
on_release:
if not(screen.scale > 4.0): screen.scale += 0.05
# Carousel:
# AndroidPhoneScreen:
# Button:
# text: 'Hello'
# IphoneScreen:
# Button:
# text: 'Hello'
# IpadScreen:
# Button:
# text: 'Hello'
# AndriodTabScreen:
# Button:
# text: 'Hello'
''')
)
================================================
FILE: kivystudio/components/sibebar/__init__.py
================================================
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.label import Label
from kivy.uix.behaviors import ToggleButtonBehavior
from kivy.uix.screenmanager import ScreenManager
from kivy.lang import Builder
from kivystudio.behaviors import HoverInfoBehavior
from .fileexplorer import FileExplorer
from .gitmanager import GitManager
from .generalsearch import GeneralSearch
from kivy import properties as prop
import os
filepath = os.path.dirname(__file__)
Builder.load_file(os.path.join(filepath,'sidebar.kv'))
__all__ = ('SideBar',)
class SideBar(BoxLayout):
def __init__(self, **k):
super(SideBar, self).__init__(**k)
self.fileexplorer = FileExplorer()
self.gitmanager = GitManager()
self.generalsearch = GeneralSearch()
def toggle_bar(self, tab):
if tab.state=='down':
if len(self.children) > 1:
self.remove_widget(self.children[0])
self.width = '46dp'
tab_bar = getattr(self, tab.tab_type)
self.width += tab_bar.width
self.add_widget(tab_bar)
else:
self.width = '46dp'
if len(self.children) > 1:
self.remove_widget(self.children[0])
class SideButter(HoverInfoBehavior, ToggleButtonBehavior, Label):
'''
buttons on the sidebar
'''
def on_hover(self, *a):
if self.hover:
self.color = (1,1,1,1)
elif not self.hover and self.state == 'normal':
self.color = (.5,.5,.5,1)
return super(SideButter, self).on_hover(*a)
class SideToggleBar(ScreenManager):
'''ScreenManager of the sidebar
'''
================================================
FILE: kivystudio/components/sibebar/fileexplorer/__init__.py
================================================
from kivy.uix.treeview import TreeViewLabel, TreeView
from kivy.uix.screenmanager import Screen
from kivy.uix.button import Button
from kivy.lang import Builder
from kivy.properties import ObjectProperty
from .filewidgets import TreeViewFile
import os
from os.path import join, split, dirname
class FileView(TreeView):
def on_touch_down(self, touch):
node = self.get_node_at_pos(touch.pos)
if not node:
return
if node.disabled:
return
# toggle node or selection ?
self.toggle_node(node)
self.select_node(node)
# node.dispatch('on_touch_down', touch)
return True
class FileExplorer(Screen):
tree_view = ObjectProperty(None)
def __init__(self, **k):
super(FileExplorer, self).__init__(**k)
# self.load_directory('widgets') #test
def load_directory(self, directory):
'''
load a directory all it files and subdirectory
on the the tree view '''
tree_view = self.tree_view
for node in tree_view.iterate_all_nodes(node=None):
tree_view.remove_node(node)
dir_nodes = {}
for dirpath, dirnames, filenames in os.walk(directory):
try:
top= dir_nodes[dirname(dirpath)]
except KeyError:
top=None
parent = tree_view.add_node(TreeViewLabel(text=split(dirpath)[1]), top)
dir_nodes[dirpath] = parent
for file in filenames:
tree_view.add_node(TreeViewFile(text=file,path=join(dirpath,file)),
parent)
Builder.load_string('''
:
width: self.texture_size[0]
shorten_from: 'right'
shorten: True
height: dp(24)
text_size: self.width, None
:
tree_view: tree_view
canvas.before:
Color:
rgba: .1,.1,.1,1
Rectangle:
size: self.size
size_hint_x: None
width: '160dp'
GridLayout:
cols: 1
Label:
text: 'Explorer!'
font_size: '16dp'
size_hint_y: None
height: '32dp'
ScrollView:
id: tree_scroll
bar_width: 10
scroll_type: ['bars', 'content']
FileView:
id: tree_view
indent_level: '12dp'
indent_start: '16dp'
size_hint: 1, None
height: max(tree_scroll.height, self.minimum_height)
''')
================================================
FILE: kivystudio/components/sibebar/fileexplorer/filewidgets.py
================================================
from kivy.uix.treeview import TreeViewNode, TreeViewLabel
from kivy.uix.boxlayout import BoxLayout
from kivy.lang import Builder
from kivy.properties import ListProperty, StringProperty, BooleanProperty
from kivystudio.tools.iconfonts import icon
from kivystudio.tools import quicktools
from kivystudio.behaviors import HoverBehavior
import os
# class TreeViewFile(HoverBehavior, TreeViewNode, BoxLayout):
class TreeViewFile(TreeViewLabel):
hover_color = ListProperty([.15,.15,.15,0])
' default color when the mouse hovers over the widget'
path = StringProperty('')
' path to the file or directory '
display_name = StringProperty('')
' name displayed for the file or directory '
display_icon = StringProperty('')
''' icon displayed on the right side of the file
or directory '''
def on_hover(self, *a):
if self.hover:
self.hover_color = (.14,.14,.14,.8)
else:
self.hover_color = (.15,.15,.15,0)
def on_is_selected(self, *args):
if self.is_selected:
quicktools.open_file(self.path)
def on_path(self, *a):
if os.path.isdir(self.path):
self.display_icon = icon('fa-caret-right',16)
elif os.path.isfile(self.path):
self.display_icon = icon('fa-file-code-o', 16)
self.display_name = os.path.split(self.path)[1]
Builder.load_string('''
#: import icon kivystudio.tools.iconfonts.icon
:
size_hint_y: None
height: '24dp'
canvas.before:
Color:
rgba: self.hover_color
Rectangle:
size: self.size
pos: self.pos
:
size_hint_y: None
height: '24dp'
canvas.before:
Clear
Color:
rgba: self.hover_color
Rectangle:
size: self.size
pos: self.pos
canvas.after:
Clear
IconLabel:
size_hint_x: None
width: '24dp'
text: root.display_icon
color: .2,.5,1,1
Label:
text: root.display_name
''')
================================================
FILE: kivystudio/components/sibebar/generalsearch.py
================================================
from kivy.uix.screenmanager import Screen
from kivy.lang import Builder
Builder.load_string('''
:
size_hint_x: None
width: '160dp'
canvas.before:
Color:
rgba: .1,.1,.1,1
Rectangle:
size: self.size
Label:
text: 'Search....'
''')
class GeneralSearch(Screen):
pass
================================================
FILE: kivystudio/components/sibebar/gitmanager.py
================================================
from kivy.uix.screenmanager import Screen
from kivy.lang import Builder
Builder.load_string('''
:
size_hint_x: None
width: '160dp'
canvas.before:
Color:
rgba: .1,.1,.1,1
Rectangle:
size: self.size
Label:
text: 'Git'
''')
class GitManager(Screen):
pass
================================================
FILE: kivystudio/components/sibebar/sidebar.kv
================================================
#: import icon kivystudio.tools.iconfonts.icon
:
size_hint_x: None
width: '46dp'
FloatLayout:
size_hint_x: None
width: '46dp'
canvas.before:
Color:
rgba: .12,.12,.12,1
Rectangle:
size: self.size
pos: self.pos
GridLayout:
cols: 1
size_hint_y: None
height: self.minimum_height
pos_hint: {'center_x': .5, 'center_y': .5}
SideButter:
info_text: 'Search (Ctrl+Shift+F)'
text: '%s'%(icon('fa-search'))
tab_type: 'generalsearch'
on_state: root.toggle_bar(self)
SideButter:
id: explorer_btn
info_text: 'Explorer (Ctrl+Shift+E)'
text: '%s'%(icon('fa-folder-open'))
tab_type: 'fileexplorer'
on_state: root.toggle_bar(self)
SideButter:
info_text: 'Settings (Ctrl+Shift+S)'
text: '%s'%(icon('fa-cogs'))
tab_type: 'gitmanager'
on_state: root.toggle_bar(self)
SideButter:
info_text: 'Source Control (Ctrl+Shift+G)'
text: '%s'%(icon('fa-github'))
tab_type: 'gitmanager'
on_state: root.toggle_bar(self)
:
size_hint: 1, None
height: '46dp'
font_size: '32dp'
markup: True
color: .5,.5,.5,1
on_state:
if self.state=='down': self.color=(1,1,1,1)
else: self.color=(.5,.5,.5,1)
group: 'side_butters'
================================================
FILE: kivystudio/components/terminal/__init__.py
================================================
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.behaviors import ToggleButtonBehavior
from kivy.uix.label import Label
from kivy.uix.screenmanager import Screen
from kivy import properties as prop
from kivy.clock import Clock
from kivy.lang import Builder
from kivystudio.libs.resizablebehavior import ResizableBehavior
from .logger_space import ErrorLogger
class TerminalSpace(ResizableBehavior, BoxLayout):
manager = prop.ObjectProperty(None)
''' Instance of screen manager used '''
tab_container = prop.ObjectProperty(None)
''' instance of a gridlayout where tha tab lays'''
state = prop.OptionProperty('open', options=['open', 'close'])
def __init__(self, **k):
super(TerminalSpace, self).__init__(**k)
self.logger = ErrorLogger()
self.add_widget(self.logger, title='Logs')
def add_widget(self, widget, title=''):
if len(self.children) > 1:
tab = TerminalTab()
tab.text=title
tab.name=title
tab.bind(state=self.tab_state)
self.tab_container.add_widget(tab)
Clock.schedule_once(lambda dt: setattr(tab, 'state', 'down'))
screen = Screen(name=title)
screen.add_widget(widget)
self.manager.add_widget(screen)
else:
super(TerminalSpace, self).add_widget(widget)
def tab_state(self, tab, state):
panel = self.manager.get_screen(tab.name).children[0]
if state=='down':
self.manager.current = tab.name
for item in panel.top_pannel_items:
self.top_pannel.add_widget(item, 2)
else:
for item in panel.top_pannel_items:
if item in self.top_pannel.children:
self.top_pannel.remove_widget(item)
def on_state(self, *args):
if self.state=='open':
self.height = self.norm_height
else:
self.height='48dp'
def toggle_state(self):
if self.state=='open':
self.state='close'
else:
self.state='open'
class TerminalTab(ToggleButtonBehavior, Label):
def on_state(self, *a):
if self.state=='down':
self.text = "[u]" + self.text + "[/u]"
self.color = (.9,.9,.9,1)
else:
self.text = self.text.replace('[u]','').replace('[/u]','')
self.color = (.5,.5,.5,1)
Builder.load_string('''
:
resizable_up: True
tab_container: tab_container
top_pannel: top_pannel
manager: manager
orientation: 'vertical'
pos_hint: {'y': 0, 'center_x': .5}
size_hint_y: None
max_norm_height: dp(380)
norm_height: dp(200)
height: self.norm_height
on_height:
if self.height > self.max_norm_height: height_tog.state='down'
canvas.before:
Color:
rgba: .12,.12,.12,1
Rectangle:
size: self.size
pos: self.pos
Color:
rgba: 1,1,1,1
Line:
points: [self.x,self.top,self.right,self.top]
width: dp(1.4)
BoxLayout:
size_hint_y: None
height: '48dp'
GridLayout:
id: tab_container
rows: 1
BoxLayout:
id: top_pannel
size_hint_x: None
width: self.minimum_width
IconToggleLabel:
id: height_tog
icon_normal: 'fa-angle-up'
icon_down: 'fa-angle-down'
icon: self.icon_normal
icon_size: 30
size_hint_x: None
width: '32dp'
on_state:
root.state='open'
if self.state=='down': root.height=root.max_norm_height
else: root.height=root.norm_height
IconLabelButton:
icon: 'fa-close'
size_hint_x: None
width: '32dp'
on_release: root.state='close'
ScreenManager:
id: manager
:
allow_no_selection: True
group: '__terminal_tab__'
size_hint_x: None
width: '94dp'
markup: True
:
icon: 'fa-close'
size_hint_x: None
width: '32dp'
''')
================================================
FILE: kivystudio/components/terminal/command_terminal.py
================================================
================================================
FILE: kivystudio/components/terminal/logger_space.py
================================================
from kivy.uix.boxlayout import BoxLayout
from kivy import properties as prop
from kivy.lang import Builder
from kivy.factory import Factory
MAX_LOG_LINES = 260
class ErrorLogger(BoxLayout):
text = prop.StringProperty()
''' property where the logs are stored '''
top_pannel_items = prop.ListProperty()
def __init__(self, **kwargs):
super().__init__(**kwargs)
clearbtn = Factory.TopPanelButton(icon='fa-trash-o')
clearbtn.bind(on_release=self.clear_logs)
self.top_pannel_items.append(clearbtn)
def log(self, msg):
lines = self.text.splitlines()
if len(lines) > MAX_LOG_LINES: # clean previous logs
self.text = '\n'.join(lines[int(MAX_LOG_LINES/2):])
self.text += msg+'\n'
self.ids.scroll.scroll_y = 0
def clear_logs(self, *args):
self.text = ''
class InternalErrorLogger(BoxLayout):
text = prop.StringProperty('Hello '*39)
Builder.load_string('''
:
ScrollView:
id: scroll
bar_width: '10dp'
scroll_type: ['bars', 'content']
Label:
text: root.text
text_size: self.width, None
size_hint_y: None
height: self.texture_size[1]
valign: 'top'
halign: 'left'
color: .8,.8,.8,1
markup: True
font_size: '14sp'
''')
================================================
FILE: kivystudio/components/topmenu/__init__.py
================================================
from kivy.uix.gridlayout import GridLayout
from kivy.uix.label import Label
from kivy.uix.dropdown import DropDown
from kivy.uix.button import Button
from kivy.uix.behaviors import ToggleButtonBehavior, ButtonBehavior
from kivy.clock import Clock
from kivy.lang import Builder
from kivy.properties import ListProperty, BooleanProperty, ObjectProperty
from kivystudio.behaviors import HoverBehavior
from . import dropmenu
__all__ = ('TopMenu',)
class TopMenu(GridLayout):
drop_on_hover = BooleanProperty(False)
menu = ObjectProperty()
def __init__(self, **kwargs):
super(TopMenu, self).__init__(**kwargs)
# def drop(self, btn, hover):
# if hover and not self.dropdown.parent:
# self.dropdown.drop_list=btn.drop_list
# self.dropdown.open(btn)
# else:
# Clock.schedule_once(self.decide_drop)
# def decide_drop(self, dt):
# if not self.dropdown.hover:
# self.dropdown.dismiss()
def open_menu(self, widget):
if self.menu:
x = self.menu.dismiss()
menu_name = self.remove_underscore(widget.text) + 'TopMenu'
setattr(self, 'menu_name', getattr(dropmenu, menu_name)())
self.menu = getattr(self, 'menu_name')
self.menu.open(self.children[widget.index])
def remove_underscore(self, text):
return text.replace('[u]','').replace('[/u]','')
def add_underscore(self,text):
return "[u]" + text + "[/u]"
class TopMenuItem(HoverBehavior, ButtonBehavior, Label):
def on_hover(self, *args):
widget, hover = args
if hover:
widget.text = self.parent.add_underscore(widget.text)
widget.color = (.1,.5,.1,1)
else:
widget.text = self.parent.remove_underscore(widget.text)
widget.color = (0,0,0,1)
Builder.load_string('''
:
size_hint_y: None
height: '24dp'
rows: 1
canvas.before:
Color:
rgba: .85,.85,.85,1
Rectangle:
size: self.size
pos: self.pos
TopMenuItem:
text: 'File'
index:4
on_release: root.open_menu(self)
TopMenuItem:
text: 'Edit'
index:3
on_release: root.open_menu(self)
TopMenuItem:
text: 'View'
index:2
on_release: root.open_menu(self)
TopMenuItem:
text: 'Selection'
index:1
on_release: root.open_menu(self)
TopMenuItem:
text: 'Help'
index:0
on_release: root.open_menu(self)
:
size_hint_x: None
width: '60dp'
markup: True
color: (0,0,0,1)
''')
================================================
FILE: kivystudio/components/topmenu/dropmenu.kv
================================================
#: import icon kivystudio.tools.iconfonts.icon
:
auto_width: False
width: '300dp'
MenuButton:
on_release: root.new_file()
MenuLabel:
text: 'New File'
MenuLabel:
halign:'right'
text: 'Ctrl+N'
type: 'shortcut'
MenuButton:
on_release: root.open_file()
MenuLabel:
text: 'Open File'
MenuLabel:
halign:'right'
text: 'Ctrl+O'
type: 'shortcut'
MenuButton:
on_release: root.open_folder()
MenuLabel:
text: 'Open Folder'
MenuLabel:
halign:'right'
text: '[Ctrl+K Ctrl+O]'
type: 'shortcut'
MenuButton:
on_release: root.save()
MenuLabel:
text: 'Save'
MenuLabel:
halign:'right'
text: 'Ctrl+S'
type: 'shortcut'
ToggleMenuButton:
text: 'Auto Save'
state: 'down' if settings.auto_save else 'normal'
on_state:
if self.state=='down': settings.auto_save=1
else: settings.auto_save=0
ToggleMenuButton:
text: 'Auto emulation'
state: 'down' if settings.auto_emulate else 'normal'
on_state:
if self.state=='down': settings.auto_emulate=1
else: settings.auto_emulate=0
MenuButton:
on_release: root.save_as()
MenuLabel:
text: 'Save as'
MenuLabel:
halign:'right'
text: 'Ctrl+Shift+S'
type: 'shortcut'
MenuButton:
on_release: root.save_all()
MenuLabel:
text: 'Save All'
MenuButton:
on_release: root.exit_window()
MenuLabel:
text: 'Exit'
MenuLabel:
halign:'right'
text: 'Ctrl+Q'
type: 'shortcut'
:
auto_width: False
width: '300dp'
MenuButton:
on_release:
MenuLabel:
text: 'Edit File'
MenuLabel:
halign:'right'
text: ''
type: 'shortcut'
:
auto_width: False
width: '300dp'
MenuButton:
on_release:
MenuLabel:
text: 'View File'
MenuLabel:
halign:'right'
text: ''
type: 'shortcut'
:
auto_width: False
width: '300dp'
MenuButton:
on_release:
MenuLabel:
text: 'Selection File'
MenuLabel:
halign:'right'
text: ''
type: 'shortcut'
:
auto_width: False
width: '300dp'
MenuButton:
on_release: print('help was clicked!')
MenuLabel:
text: 'Help'
:
text_size: self.size
halign: 'left'
valign: 'middle'
padding: '20dp', 0
type: 'text'
color: ((0/255,0/255,0/255,.9) if self.type=='text' else (160/255,160/255,160/255,.9)) if self.parent else (236/255,243.255,1,.5)
:
size_hint_y: None
height: '30dp'
canvas_color: 1,1,1,1
text_colors: ((.1,.1,.1,1), (.5,.5,.5,1))
on_hover:
if self.hover: self.canvas_color = .2,.5,1,1; self.text_colors=((1,1,1,1),(1,1,1,1))
else: self.canvas_color = 1,1,1,1; self.text_colors=((0,0,0,1), (.5,.5,.5,1))
canvas.before:
Color:
rgba: self.canvas_color
Rectangle:
size: self.size
pos: self.pos
:
size_hint_y: None
height: '30dp'
canvas_color: 1,1,1,1
text_colors: (.1,.1,.1,1)
text: ''
on_hover:
if self.hover: self.canvas_color = .2,.5,1,1;self.state;
else: self.canvas_color = (1,1,1,1)
on_state: tick.state=self.state
canvas.before:
Color:
rgba: self.canvas_color
Rectangle:
size: self.size
pos: self.pos
MenuLabel:
text: root.text
IconToggleLabel:
size_hint_x: None
width: '48dp'
id: tick
color: .2,.2,.2,1
on_state:
if self.state=='normal': self.text='';print(self.state)
else: self.text = icon('fa-check', 16)
on_parent: self.state='down'
================================================
FILE: kivystudio/components/topmenu/dropmenu.py
================================================
from kivy.uix.behaviors import ButtonBehavior, ToggleButtonBehavior
from kivy.uix.boxlayout import BoxLayout
from kivy.lang import Builder
from kivystudio.behaviors import HoverBehavior
from kivystudio.widgets.dropdown import DropDownBase
from kivystudio import tools
tools.load_kv(__file__,'dropmenu.kv')
class MenuButton(HoverBehavior, ButtonBehavior, BoxLayout):
def on_hover(self, widget, hover):
if hover:
self.canvas_color = .2,.5,1,1
if len(self.children) > 1:
self.children[0].color = (1,1,1,1)
else:
self.canvas_color = (1,1,1,1)
if len(self.children) > 1:
self.children[0].color = (.5,.5,.5,1);
class ToggleMenuButton(HoverBehavior, ToggleButtonBehavior, BoxLayout):
pass
class FileTopMenu(DropDownBase):
def __init__(self, **k):
super(FileTopMenu, self).__init__(**k)
def new_file(self):
tools.quicktools.open_new_file()
def open_file(self):
tools.quicktools.open_file()
def open_folder(self):
tools.quicktools.open_file()
def open_recent(self):
tools.quicktools.open_recent()
def save(self):
tools.quicktools.save()
def save_all(self):
tools.quicktools.save_all()
def save_as(self):
tools.quicktools.save_as()
def exit_window(self):
tools.quicktools.exit_window()
class EditTopMenu(DropDownBase):
def __init__(self, **k):
super(EditTopMenu, self).__init__(**k)
class ViewTopMenu(DropDownBase):
def __init__(self, **k):
super(ViewTopMenu, self).__init__(**k)
class SelectionTopMenu(DropDownBase):
def __init__(self, **k):
super(SelectionTopMenu, self).__init__(**k)
class HelpTopMenu(DropDownBase):
def __init__(self, **k):
super(HelpTopMenu, self).__init__(**k)
================================================
FILE: kivystudio/libs/__init__.py
================================================
================================================
FILE: kivystudio/libs/resizablebehavior/LICENSE
================================================
MIT License
Copyright (c) 2016 Kivy Garden
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: kivystudio/libs/resizablebehavior/README.md
================================================
# Resizable Behavior
A behavior for kivy widgets that allows them to be resized by touching/clickin on a corner and dragging.
[Youtube demostration video](https://www.youtube.com/watch?v=8VqLV4McmK0)
Below is also a **screenshot of the included resizable widget application**.

## Usage
Import and inherit like any other kivy behavior
```python
from kivy.garden.resizablebehavior import ResizableBehavior
from kivy.uix.button import Button
class ResizableButton(ResizableBehavior, Button):
pass
```
Enable / disable resizing of a specific side in kwargs or after
```python
res_button = ResizableButton(
resizable_left = False,
resizable_right = True,
resizable_up = False,
resizable_down = True)
res_button.resizable_left = False
res_button.resizable_right = True
res_button.resizable_up = False
res_button.resizable_down = True
```
Lock / unlock resizing
```python
res_button.resize_lock = True
```
Adjust the size of resizable border in kwargs or after
```python
res_button = ResizableButton(resizable_border=8999)
res_button.resizable_border = 100
```
Offset the resizable_border (by default it is inside the widget) in kwargs or after
```python
res_button = ResizableButton(resizable_border_offset=100)
#A value of resizable_border * 0.5 will center it on the edge of the ResizableButton
res_button.resizable_border_offset = res_button.resizable_border * 0.5
```
Set min and max sizes in kwargs or after
```python
res_button = ResizableButton(
min_resizable_width = 100,
min_resizable_height = 101,
max_resizable_width = 102,
max_resizable_height = 103)
res_button.min_resizable_width = 100
res_button.min_resizable_height = 101
res_button.max_resizable_width = 102
res_button.max_resizable_height = 103
```
Enable / disable the cursor
```python
res_button.set_cursor_mode(0) # Disabled
res_button.set_cursor_mode(1) # Enabled
# SDL2 system cursors might be added to kivy core in future
```
Change the size of the cursor
```python
res_button.set_cursor_size(width_int, height_int)
```
Change cursor icons
```python
res_button.set_cursor_icons(
'img/1.png', # The horizontal icon
'img/2.png', # The 45 degree clockwise icon
'img/3.png', # The 90 degree clockwise icon
'img/4.png') # The 135 degree clockwise icon
```
================================================
FILE: kivystudio/libs/resizablebehavior/__init__.py
================================================
from .resize import ResizableBehavior
================================================
FILE: kivystudio/libs/resizablebehavior/modal_cursor.py
================================================
from kivy.properties import BooleanProperty, StringProperty, ListProperty
from kivy.graphics import InstructionGroup
from kivy.uix.modalview import ModalView
from kivy.graphics import Rectangle
from kivy.core.window import Window
from kivy.uix.widget import Widget
from kivy.metrics import dp, cm
from time import time
import os
path = os.path.split(os.path.realpath(__file__))[0]
class CursorModalView(ModalView):
'''
The CursorModalView is the parent of ResizeCursor
'''
last_opened = 0.0
def __init__(self, **kwargs):
super(CursorModalView, self).__init__(**kwargs)
self.auto_dismiss = False
self.size_hint = (None, None)
self.background_color = (0, 0, 0, 0)
self.pos = (-9999, -9999)
self.cursor = ResizeCursor()
self.add_widget(self.cursor)
self.open()
def open(self, *largs):
self._window = self._search_window()
if not self._window:
Logger.warning('ModalView: cannot open view, no window found.')
return
if self.parent:
self.parent.remove_widget(self)
self._window.add_widget(self)
def put_on_top(self, *args):
self.dismiss()
self.open()
def on_hidden(self, val):
# View has to be reopened to get it on top of other widgets
timenow = time()
if not val and timenow > self.last_opened + 1:
self.dismiss()
self.open()
self.last_opened = timenow
def on_touch_down(self, *args):
pass
def on_touch_up(self, *args):
pass
def on_touch_move(self, *args):
pass
class ResizeCursor(Widget):
'''
The ResizeCursor is the mouse cursor
'''
hidden = BooleanProperty(True)
'''State of cursors visibility
It is switched to True when mouse is inside the widgets resize border
and False when it isn't.
:attr:`hidden` is a :class:`~kivy.properties.BooleanProperty` and
defaults to True.
'''
resize_icon_paths = ListProperty([
'{}/resize_horizontal.png'.format(path),
'{}/resize2.png'.format(path),
'{}/resize_vertical.png'.format(path),
'{}/resize1.png'.format(path),
])
'''Cursor icon paths,
:attr:`resize_icon_paths` is a :class:`~kivy.properties.ListProperty` and
defaults to [
'resize_horizontal.png',
'resize2.png',
'resize_vertical.png',
'resize1.png',
]
'''
grabbed_by = None
'''Object reference.
Is used to prevent attribute changes from multiple widgets at same time.
:attr:`grabbed_by` defaults to None.
'''
sides = ()
source = StringProperty('')
def __init__(self, **kwargs):
super(ResizeCursor, self).__init__(**kwargs)
self.size_hint = (None, None)
self.pos_hint = (None, None)
self.source = ''
self.rect = Rectangle(pos=(-9998,-9998), size=(1, 1))
self.size = (dp(22), dp(22))
self.pos = [-9998, -9998]
# Makes an instruction group with a rectangle and
# loads an image inside it
# Binds its properties to mouse positional changes and events triggered
instr = InstructionGroup()
instr.add(self.rect)
self.canvas.after.add(instr)
self.bind(pos=lambda obj, val: setattr(self.rect, 'pos', val))
self.bind(source=lambda obj, val: setattr(self.rect, 'source', val))
self.bind(hidden=lambda obj, val: self.on_mouse_move(Window.mouse_pos))
Window.bind(mouse_pos=lambda obj, val: self.on_mouse_move(val))
def on_size(self, obj, val):
self.rect.size = val
def on_hidden(self, obj, val):
if not self.disabled:
self.parent.on_hidden(val)
if val:
Window.show_cursor = True
else:
Window.show_cursor = False
def on_mouse_move(self, val):
if self.hidden or self.disabled or not self.source:
if self.pos[0] != -9999:
self.pos[0] = -9999
else:
self.pos[0] = val[0] - self.width / 2.0
self.pos[1] = val[1] - self.height / 2.0
def change_side(self, left, right, up, down):
# Changes images when ResizableBehavior.hovering_resizable
# state changes
if self.disabled:
return
if not self.hidden and self.sides != (left, right, up, down):
if left and up or right and down:
self.source = self.resize_icon_paths[1]
elif left and down or right and up:
self.source = self.resize_icon_paths[3]
elif left or right:
self.source = self.resize_icon_paths[0]
elif up or down:
self.source = self.resize_icon_paths[2]
else:
if not any((left, right, up, down)):
self.pos[0] = -9999
self.sides = (left, right, up, down)
def grab(self, wid):
self.grabbed_by = wid
def ungrab(self, wid):
if self.grabbed_by == wid:
self.grabbed_by = None
def on_disabled(self, obj, val):
if not val:
Window.show_cursor = True
================================================
FILE: kivystudio/libs/resizablebehavior/resize.py
================================================
'''
Resizable Behavior
===============
The :class:`~kivy.uix.behaviors.resize.ResizableBehavior`
`mixin `_ class provides Resize behavior.
When combined with a widget, dragging at the resize enabled widget edge
defined by the
:attr:`~kivy.uix.behaviors.resize.ResizableBehavior.resizable_border`
will resize the widget.
For an overview of behaviors, please refer to the :mod:`~kivy.uix.behaviors`
documentation.
Example
-------
The following example adds resize behavior to a sidebar to make it resizable
from kivy.app import App
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.behaviors import ResizableBehavior
from kivy.uix.label import Label
from kivy.metrics import cm
from kivy.uix.button import Button
from kivy.graphics import *
class ResizableSideBar(ResizableBehavior, BoxLayout):
def __init__(self, **kwargs):
super(ResizableSideBar, self).__init__(**kwargs)
self.bg = Rectangle(pos=self.pos, size=self.size)
self.resizable_right = True
for x in range(1, 10):
lbl = Label(size_hint=(1, None), height=(cm(1)))
lbl.text = 'Text '+str(x)
self.add_widget(lbl)
self.bind(size=lambda obj, val: setattr(self.bg, 'size', val))
instr = InstructionGroup()
instr.add(Color(0.6, 0.6, 0.7, 1))
instr.add(self.bg)
self.canvas.before.add(instr)
class Sample(FloatLayout):
def __init__(self, **kwargs):
super(Sample, self).__init__(**kwargs)
sb = ResizableSideBar(orientation='vertical', size_hint=(None, 1))
sb.width = cm(4)
self.add_widget(sb)
class SampleApp(App):
def build(self):
return Sample()
SampleApp().run()
See :class:`~kivy.uix.behaviors.ResizableBehavior` for details.
'''
from kivy.clock import Clock
from kivy.properties import BooleanProperty, NumericProperty
from kivy.metrics import dp, cm
from .modal_cursor import CursorModalView
from kivy.core.window import Window
from kivy.logger import Logger
from kivy.app import App
__all__ = ('ResizableBehavior', )
_modalview = CursorModalView()
class ResizableBehavior(object):
'''
The ResizableBehavior `mixin `_
class provides Resize behavior.
When combined with a widget, dragging at the resize enabled widget edge
defined by the
:attr:`~kivy.uix.behaviors.resize.ResizableBehavior.resizable_border`
will resize the widget. Please see the :mod:`drag behaviors module
` documentation for more information.
'''
hovering_resizable = BooleanProperty(False)
'''State of mouse hover.
It is switched to True when mouse is inside the widgets resize border
and False when it isn't.
:attr:`hovering_resizable` is a :class:`~kivy.properties.BooleanProperty`
and defaults to False.
'''
resizable_border = NumericProperty(dp(20))
'''Widgets resizable border size on each side.
Minimum resizing size is limited to resizable_border * 3 on all sides
:attr:`resizable_border` is a :class:`~kivy.properties.NumericProperty` and
defaults to 20 dp.
'''
resizable_border_offset = NumericProperty(0)
'''Positive values move the resizable_border outside of the widget
and negative values put it closer to the center of the widget.
A value of resizable_border * 0.5 will center it on the widgets border,
which is the most expected behavior, but can sometimes interfere with
nearby widgets.
:attr:`resizable_border_offset` is a
:class:`~kivy.properties.NumericProperty` and defaults to 0.
'''
min_resizable_width = NumericProperty(0)
'''Minimum width
:attr:`min_resizable_width` is a :class:`~kivy.properties.NumericProperty`
and defaults to 0 (disabled).
'''
min_resizable_height = NumericProperty(0)
'''Minimum height
:attr:`min_resizable_height` is a :class:`~kivy.properties.NumericProperty`
and defaults to 0 (disabled).
'''
max_resizable_width = NumericProperty(0)
'''Maximum width
:attr:`max_resizable_width` is a :class:`~kivy.properties.NumericProperty`
and defaults to 0 (disabled).
'''
max_resizable_height = NumericProperty(0)
'''Maximum height
:attr:`max_resizable_height` is a :class:`~kivy.properties.NumericProperty`
and defaults to 0 (disabled).
'''
resizable_left = BooleanProperty(False)
'''Enable / disable resizing on left side
:attr:`resizable_left` is a :class:`~kivy.properties.BooleanProperty` and
defaults to False.
'''
resizable_right = BooleanProperty(False)
'''Enable / disable resizing on right side
:attr:`resizable_right` is a :class:`~kivy.properties.BooleanProperty` and
defaults to False.
'''
resizable_up = BooleanProperty(False)
'''Enable / disable resizing on upper side
:attr:`resizable_up` is a :class:`~kivy.properties.BooleanProperty` and
defaults to False.
'''
resizable_down = BooleanProperty(False)
'''Enable / disable resizing on lower side
:attr:`resizable_down` is a :class:`~kivy.properties.BooleanProperty` and
defaults to False.
'''
resizing_left = BooleanProperty(False)
'''A State which is enabled/disabled depending on the position relative to
the left resize border
It is switched to True when mouse is inside the left resize border and
False when it isn't.
It adjusts the mouse cursor and manages resizing when touch is moved.
:attr:`resizing_left` is a :class:`~kivy.properties.BooleanProperty` and
defaults to False.
'''
resizing_right = BooleanProperty(False)
'''A State which is enabled/disabled depending on the position relative to
the right resize border
It is switched to True when mouse is inside the right resize border and
False when it isn't.
It adjusts the mouse cursor and manages resizing when touch is moved.
:attr:`resizing_right` is a :class:`~kivy.properties.BooleanProperty` and
defaults to False.
'''
resizing_up = BooleanProperty(False)
'''A State which is enabled/disabled depending on the position relative
to the upper resize border
It is switched to True when mouse is inside the upper resize border and
False when it isn't.
It adjusts the mouse cursor and manages resizing when touch is moved.
:attr:`resizing_up` is a :class:`~kivy.properties.BooleanProperty` and
defaults to False.
'''
resizing_down = BooleanProperty(False)
'''A State which is enabled/disabled depending on the position relative
to the lower resize border
It is switched to True when mouse is inside the lower resize border and
False when it isn't.
It adjusts the mouse cursor and manages resizing when touch is moved.
:attr:`resizing_down` is a :class:`~kivy.properties.BooleanProperty` and
defaults to False.
'''
resizing = BooleanProperty(False)
'''State of widget resizing.
It is switched to True when a resize border is touched and back to
False when it is released.
It manages resizing when touch is moved.
:attr:`resizing` is a :class:`~kivy.properties.BooleanProperty` and
defaults to False.
'''
can_move_resize = BooleanProperty(True)
'''Move widget when resizing down or left.
To keep position on screen in a floatlayout,
actual postition has to be adjusted.
Resizing and changing position variables is problematic
inside movement restricting widgets,
(StackLayout, BoxLayout, others) this property manages that.
:attr:`can_move_resize` is a :class:`~kivy.properties.BooleanProperty` and
defaults to True.
'''
resize_lock = BooleanProperty(False)
'''Enable / disable resizing
:attr:`resize_lock` is a :class:`~kivy.properties.BooleanProperty` and
defaults to False.
'''
cursor = _modalview.cursor
def __init__(self, **kwargs):
super(ResizableBehavior, self).__init__(**kwargs)
Window.bind(mouse_pos=self.on_mouse_move)
Clock.schedule_once(_modalview.put_on_top, 0)
self.oldpos, self.oldsize = [], []
def on_enter_resizable(self):
self.cursor.hidden = False
def on_leave_resizable(self):
self.cursor.hidden = True
def on_mouse_move(self, _, pos):
if self.resize_lock:
return
if self.cursor and self.cursor.grabbed_by is None:
oldhover = self.hovering_resizable
pos = self.to_widget(pos[0]+self.pos[0], pos[1]+self.pos[1]) #added by Snu
self.hovering_resizable = self.check_resizable_side(pos[0], pos[1])
if oldhover != self.hovering_resizable:
if self.hovering_resizable:
self.on_enter_resizable()
else:
self.on_leave_resizable()
def check_resizable_side(self, x, y):
# Add small performance increase when distance is too high
# for possible hovering
if abs(self.x - x) > self.width + self.resizable_border_offset:
return False
elif abs(self.y - y) > self.height + self.resizable_border_offset:
return False
startx = self.x - self.resizable_border_offset
endx = self.right + self.resizable_border_offset
starty = self.y - self.resizable_border_offset
endy = self.top + self.resizable_border_offset
if self.resizable_left:
self.resizing_left = False
if startx <= x <= startx + self.resizable_border:
if starty <= y <= endy:
self.resizing_left = True
if self.resizable_right and not self.resizing_left:
self.resizing_right = False
if endx - self.resizable_border <= x <= endx:
if starty <= y <= endy:
self.resizing_right = True
if self.resizable_up:
self.resizing_up = False
if endy - self.resizable_border <= y <= endy:
if startx <= x <= endx:
self.resizing_up = True
if self.resizable_down and not self.resizing_up:
self.resizing_down = False
if starty <= y <= starty + self.resizable_border:
if startx <= x <= endx:
self.resizing_down = True
if any((self.resizing_left, self.resizing_right,
self.resizing_up, self.resizing_down)):
if self.cursor:
self.cursor.change_side(
self.resizing_left, self.resizing_right,
self.resizing_up, self.resizing_down)
return True
else:
return False
def on_touch_down(self, touch):
if not self.hovering_resizable:
return super(ResizableBehavior, self).on_touch_down(touch)
if self.resize_lock:
return super(ResizableBehavior, self).on_touch_down(touch)
if not any([
self.resizing_right, self.resizing_left,
self.resizing_down, self.resizing_up
]):
return super(ResizableBehavior, self).on_touch_down(touch)
self.oldpos = list(self.pos)
self.oldsize = list(self.size)
self.resizing = True
self.cursor.grab(self)
return True
def on_touch_move(self, touch):
if not self.resizing:
return super(ResizableBehavior, self).on_touch_move(touch)
self.resize_widget(touch)
def resize_widget(self, touch):
rb3 = self.resizable_border * 3
if self.resizing_right:
if touch.pos[0] > self.pos[0] + rb3:
self.width = touch.pos[0] - self.pos[0]
elif self.resizing_left:
if touch.pos[0] < self.oldpos[0] + self.oldsize[0] - rb3:
if self.can_move_resize:
self.pos[0] = touch.pos[0]
self.width = self.oldpos[0] - touch.pos[0] + \
self.oldsize[0]
else:
self.width = abs(touch.pos[0] - self.pos[0])
if self.width < rb3:
self.width = rb3
if self.resizing_down:
if touch.pos[1] < self.oldpos[1] + self.oldsize[1] - rb3:
if self.can_move_resize:
self.pos[1] = touch.pos[1]
self.height = self.oldpos[1] - touch.pos[1] + \
self.oldsize[1]
else:
self.height = abs(touch.pos[1] - self.pos[1])
if self.height < rb3:
self.height = rb3
elif self.resizing_up:
if touch.pos[1] > self.pos[1] + rb3:
self.height = touch.pos[1] - self.pos[1]
self.check_min_max_size(touch)
return True
def check_min_max_size(self, touch):
# Resizes widgets back to min / max when smaller / bigger
# Resets position only when it's necessary
if self.min_resizable_width:
if self.width < self.min_resizable_width:
if self.pos[0] != self.oldpos[0]:
self.width = self.min_resizable_width
self.pos[0] = self.oldpos[0] + self.oldsize[0] - self.width
else:
self.width = self.min_resizable_width
if self.max_resizable_width:
if self.width > self.max_resizable_width:
if self.pos[0] != self.oldpos[0]:
self.width = self.max_resizable_width
self.pos[0] = self.oldpos[0] + self.oldsize[0] - self.width
else:
self.width = self.max_resizable_width
if self.min_resizable_height:
if self.height < self.min_resizable_height:
if self.pos[1] != self.oldpos[1]:
self.height = self.min_resizable_height
self.pos[1] = (
self.oldpos[1] + self.oldsize[1] - self.height)
else:
self.height = self.min_resizable_height
if self.max_resizable_height:
if self.height > self.max_resizable_height:
if self.pos[1] != self.oldpos[1]:
self.height = self.max_resizable_height
self.pos[1] = (
self.oldpos[1] + self.oldsize[1] - self.height)
else:
self.height = self.max_resizable_height
def on_touch_up(self, touch):
if not self.resizing:
return super(ResizableBehavior, self).on_touch_up(touch)
self.resizing = False
self.resizing_right = False
self.resizing_left = False
self.resizing_down = False
self.resizing_up = False
self.cursor.ungrab(self)
self.on_mouse_move(None, touch.pos)
return True
def on_resize_lock(self, obj, locked):
'Resets behavior to default values when resizing is locked'
if locked:
self.resizing = False
self.resizing_right = False
self.resizing_left = False
self.resizing_down = False
self.resizing_up = False
Window.show_cursor = True
self.cursor.ungrab(self)
self.cursor.hidden = True
def set_cursor_size(self, size):
'''Default cursor size is (dp(22), dp(22)).
Use this method to change it
'''
self.cursor.size = size
def set_cursor_icons(self, hor, deg45, deg90, deg135):
'''Change cursor icon paths.
Function takes 4 arguments, first is horizontal,
next 3 are turned 45, 90, 135 degrees clockwise.
'''
self.cursor.resize_icon_paths[0] = hor
self.cursor.resize_icon_paths[1] = deg45
self.cursor.resize_icon_paths[2] = deg90
self.cursor.resize_icon_paths[3] = deg135
def set_cursor_mode(self, value):
'''Method takes expects one integer
0 - Disables the resize cursor
1 - Default mode, os cursor is hidden and replaced with resize cursor
when mouse position enters widgets resizable_border
'''
if value == 0 and not self.cursor.disabled:
self.cursor.disabled = True
elif value == 1 and self.cursor.disabled:
self.cursor.disabled = False
================================================
FILE: kivystudio/main.kv
================================================
#: import icon kivystudio.tools.iconfonts.icon
#: import settings kivystudio.settings.settings_obj
:
orientation: 'vertical'
TopMenu:
BoxLayout:
id: box
================================================
FILE: kivystudio/main.py
================================================
'''
KivyStudio main.py
entry point for the Application
'''
import sys, os
sys.path = [os.pardir] + sys.path
# from kivy.config import Config
# Config.set('modules', 'monitor', '')
from kivy.app import App
from os.path import dirname, join
from kivy.lang import Builder
from kivystudio import tools
filepath = dirname(__file__)
tools.load_kv(__file__,'main.kv')
# registering custom icons
tools.iconfonts.register('awesome_font',
join(filepath,'resources/font-awesome.ttf'),
join(filepath, 'resources/font-awesome.fontd'))
from kivystudio.assembler import Assembler
class KivyStudio(App):
def build(self):
return Assembler
def run(self):
super(KivyStudio, self).run()
studio_app = KivyStudio()
def main():
studio_app.run()
if __name__ == "__main__":
main()
================================================
FILE: kivystudio/parser/__init__.py
================================================
import os, sys
import traceback
from threading import Thread
from functools import partial
try:
from importlib import reload
except: # for py 2 compatibility
pass
from kivy.lang import Builder
from kivy.clock import mainthread
from kivy.uix.widget import Widget
from kivy.resources import resource_add_path, resource_remove_path
from kivystudio.components.emulator_area import get_emulator_area
from kivystudio.tools.logger import Logger
def emulate_file(filename, threaded=False):
if not filename:
Logger.error("KivyStudio: No file selected press Ctrl-E to select file for emulation")
return
Logger.info("Emulator: Starting Emulation on file '{}'".format(filename))
root=None
if not os.path.exists(filename):
Logger.error("KivyStudio: file {} not found".format(filename))
return
with open(filename) as fn:
file_content = fn.read()
if app_not_run_properly(file_content):
Logger.error("Emulator: App not run properly 'try running under if __name__ == '__main__':")
return
dirname=os.path.dirname(filename)
sys.path.append(dirname)
os.chdir(dirname)
resource_add_path(dirname)
get_emulator_area().screen_display.screen.clear_widgets()
if threaded:
Thread(target=partial(start_emulation, filename,
file_content, threaded=threaded)).start()
else:
start_emulation(filename, file_content, threaded=threaded)
def start_emulation(filename, file_content, threaded=False):
root = None
has_error = False
if os.path.splitext(filename)[1] =='.kv': # load the kivy file directly
try: # cacthing error with kivy files
Builder.unload_file(filename)
root = Builder.load_file(filename)
except:
has_error = True
trace = traceback.format_exc()
Logger.error("Emulator: {}".format(trace))
elif os.path.splitext(filename)[1] =='.py':
load_defualt_kv(filename, file_content)
try: # cahching error with python files
root = load_py_file(filename, file_content)
except:
has_error = True
trace = traceback.format_exc()
Logger.error("Emulator: {}".format(trace))
else:
Logger.warning("KivyStudio: can't emulate file type {}".format(filename))
if not root and not has_error:
Logger.error('Emulator: No root widget found.')
elif not isinstance(root,Widget) and not has_error:
Logger.error("KivyStudio: root instance found = '{}' and is not a widget".format(root))
elif root:
if threaded:
emulation_done(root, filename)
else:
get_emulator_area().screen_display.screen.add_widget(root)
dirname=os.path.dirname(filename)
sys.path.pop()
resource_remove_path(dirname)
@mainthread
def emulation_done(root, filename):
' add root on the main thread '
if root:
get_emulator_area().screen_display.screen.add_widget(root)
def load_defualt_kv(filename, file_content):
''' load the default kivy file
associated the the python file,
usaully lowercase of the app class
'''
app_cls_name = get_app_cls_name(file_content)
if app_cls_name is None:
return
kv_name = app_cls_name.lower()
if app_cls_name.endswith('App'):
kv_name = app_cls_name[:len(app_cls_name)-3].lower()
if app_cls_name:
file_dir = os.path.dirname(filename)
kv_filename = os.path.join(file_dir, kv_name+'.kv')
if os.path.exists(kv_filename):
try: # cacthing error with kivy files
Builder.unload_file(kv_filename)
root = Builder.load_file(kv_filename)
except:
trace = traceback.format_exc()
Logger.error("KivyStudio: You kivy file has a problem")
Logger.error("KivyStudio: {}".format(trace))
def get_app_cls_name(file_content):
lines = file_content.splitlines()
app_cls = get_import_as('from kivy.app import App', lines)
if not app_cls:
app_cls = get_import_as('from kivymd.app import MDApp', lines)
def check_app_cls(line):
line = line.strip()
return line.startswith('class') and line.endswith('(%s):'%app_cls)
found = list(filter(check_app_cls, lines))
if found:
line = found[0]
cls_name = line.split('(')[0].split(' ')[1]
return cls_name
def get_root_from_runTouch(filename):
with open(filename) as fn:
text = fn.read()
lines = text.splitlines()
run_touch = get_import_as('from kivy.base import runTouchApp', lines)
def check_run_touch(line):
line = line.strip()
return line.startswith('%s(' % run_touch)
found = list(filter(check_run_touch, lines))
if found:
line = found[0]
root_name = line.strip().split('(')[1].split(')')[0]
root_file = import_from_dir(filename)
root = getattr(reload(root_file), root_name)
return root
def load_py_file(filename, file_content):
app_cls_name = get_app_cls_name(file_content)
if app_cls_name:
root_file = import_from_dir(filename)
app_cls = getattr(reload(root_file), app_cls_name)
root = app_cls().build()
return root
run_root = get_root_from_runTouch(filename)
if run_root:
return run_root
def import_from_dir(filename):
''' force python to import this file
from the project_ dir'''
dirname, file = os.path.split(filename)
sys.path = [dirname] + sys.path
import_word = os.path.splitext(file)[0]
imported = __import__(import_word)
return imported
def get_import_as(start, lines):
''' get the variable used by user when importing as.
Ex: from kivy import platform
it will return plaform
Ex: from kivy import platform as plt
it will return plt
'''
line = list(filter(lambda line: line.strip().startswith(start), lines))
if line:
words = line[0].split(' ')
import_word = words[len(words)-1]
return import_word
def app_not_run_properly(file_content):
lines = file_content.splitlines()
run_touch = get_import_as('from kivy.base import runTouchApp', lines)
def check_run_touch(line):
return line.startswith('%s(' % run_touch)
found1 = list(filter(check_run_touch, lines))
def check_run_app(line):
app_name = get_app_cls_name(file_content)
return line.endswith('run()') and line.startswith(app_name)
found2 = list(filter(check_run_app, lines))
return found1 or found2
================================================
FILE: kivystudio/resources/font-awesome.fontd
================================================
{"fa-camera": 61488, "fa-building-o": 61687, "fa-align-left": 61494, "fa-hand-o-up": 61606, "fa-external-link-square": 61772, "fa-cubes": 61875, "fa-get-pocket": 62053, "fa-share-alt-square": 61921, "fa-comment-o": 61669, "fa-thumbs-o-down": 61576, "fa-heart": 61444, "fa-file-photo-o": 61893, "fa-list": 61498, "fa-calendar-o": 61747, "fa-euro": 61779, "fa-life-buoy": 61901, "fa-stethoscope": 61681, "fa-bookmark": 61486, "fa-barcode": 61482, "fa-th-large": 61449, "fa-square": 61640, "fa-bank": 61852, "fa-times-circle-o": 61532, "fa-tachometer": 61668, "fa-bell-o": 61602, "fa-leaf": 61548, "fa-skype": 61822, "fa-certificate": 61603, "fa-car": 61881, "fa-ship": 61978, "fa-navicon": 61641, "fa-arrows-alt": 61618, "fa-server": 62003, "fa-cab": 61882, "fa-clone": 62029, "fa-wifi": 61931, "fa-meanpath": 61964, "fa-send-o": 61913, "fa-subscript": 61740, "fa-shirtsinbulk": 61972, "fa-sticky-note": 62025, "fa-road": 61464, "fa-times-circle": 61527, "fa-cart-arrow-down": 61976, "fa-safari": 62055, "fa-file-code-o": 61897, "fa-tty": 61924, "fa-list-ul": 61642, "fa-shopping-basket": 62097, "fa-arrow-circle-o-down": 61466, "fa-file-movie-o": 61896, "fa-slideshare": 61927, "fa-toggle-left": 61841, "fa-circle-o-notch": 61902, "fa-check-circle": 61528, "fa-mars-stroke-h": 61995, "fa-chevron-circle-up": 61753, "fa-circle": 61713, "fa-y-combinator-square": 61908, "fa-gbp": 61780, "fa-umbrella": 61673, "fa-sort-numeric-asc": 61794, "fa-pencil-square": 61771, "fa-soccer-ball-o": 61923, "fa-bicycle": 61958, "fa-user-times": 62005, "fa-trash-o": 61460, "fa-recycle": 61880, "fa-bell-slash-o": 61943, "fa-share-square-o": 61509, "fa-yc-square": 61908, "fa-users": 61632, "fa-yen": 61783, "fa-unlink": 61735, "fa-ban": 61534, "fa-sort-amount-asc": 61792, "fa-film": 61448, "fa-caret-down": 61655, "fa-file-text": 61788, "fa-list-alt": 61474, "fa-th-list": 61451, "fa-life-ring": 61901, "fa-filter": 61616, "fa-bluetooth-b": 62100, "fa-caret-up": 61656, "fa-sign-out": 61579, "fa-pencil": 61504, "fa-ticket": 61765, "fa-area-chart": 61950, "fa-skyatlas": 61974, "fa-opencart": 62013, "fa-hand-o-left": 61605, "fa-dedent": 61499, "fa-html5": 61755, "fa-at": 61946, "fa-dropbox": 61803, "fa-file-zip-o": 61894, "fa-long-arrow-up": 61814, "fa-stack-overflow": 61804, "fa-product-hunt": 62088, "fa-chevron-circle-left": 61751, "fa-check-square": 61770, "fa-cloud-download": 61677, "fa-caret-square-o-down": 61776, "fa-microphone-slash": 61745, "fa-folder": 61563, "fa-keyboard-o": 61724, "fa-bar-chart-o": 61568, "fa-transgender-alt": 61989, "fa-reddit-alien": 62081, "fa-openid": 61851, "fa-sort-asc": 61662, "fa-pause-circle": 62091, "fa-archive": 61831, "fa-eraser": 61741, "fa-cart-plus": 61975, "fa-inbox": 61468, "fa-truck": 61649, "fa-file-word-o": 61890, "fa-pied-piper-alt": 61864, "fa-object-ungroup": 62024, "fa-phone-square": 61592, "fa-eye": 61550, "fa-sun-o": 61829, "fa-folder-o": 61716, "fa-rebel": 61904, "fa-bars": 61641, "fa-cc-paypal": 61940, "fa-simplybuilt": 61973, "fa-won": 61785, "fa-hand-lizard-o": 62040, "fa-balance-scale": 62030, "fa-frown-o": 61721, "fa-repeat": 61470, "fa-arrow-circle-o-up": 61467, "fa-taxi": 61882, "fa-map-o": 62072, "fa-question": 61736, "fa-meh-o": 61722, "fa-terminal": 61728, "fa-caret-left": 61657, "fa-stop": 61517, "fa-tv": 62060, "fa-spoon": 61873, "fa-th": 61450, "fa-files-o": 61637, "fa-pause": 61516, "fa-mail-reply": 61714, "fa-cutlery": 61685, "fa-linux": 61820, "fa-battery-empty": 62020, "fa-hourglass-start": 62033, "fa-pause-circle-o": 62092, "fa-arrow-circle-left": 61608, "fa-shopping-cart": 61562, "fa-map-pin": 62070, "fa-youtube-play": 61802, "fa-drupal": 61865, "fa-bed": 62006, "fa-battery-quarter": 62019, "fa-weixin": 61911, "fa-exchange": 61676, "fa-gg-circle": 62049, "fa-angle-double-up": 61698, "fa-forward": 61518, "fa-hand-pointer-o": 62042, "fa-cog": 61459, "fa-reddit-square": 61858, "fa-arrow-circle-down": 61611, "fa-battery-three-quarters": 62017, "fa-venus-double": 61990, "fa-tumblr-square": 61812, "fa-angellist": 61961, "fa-toggle-off": 61956, "fa-info": 61737, "fa-eyedropper": 61947, "fa-behance": 61876, "fa-map-signs": 62071, "fa-file-audio-o": 61895, "fa-gavel": 61667, "fa-glass": 61440, "fa-hand-o-right": 61604, "fa-cube": 61874, "fa-pinterest-square": 61651, "fa-cc-stripe": 61941, "fa-battery-full": 62016, "fa-lightbulb-o": 61675, "fa-caret-square-o-up": 61777, "fa-video-camera": 61501, "fa-jsfiddle": 61900, "fa-long-arrow-left": 61815, "fa-caret-right": 61658, "fa-volume-down": 61479, "fa-tags": 61484, "fa-arrow-up": 61538, "fa-folder-open-o": 61717, "fa-shield": 61746, "fa-dashcube": 61968, "fa-rotate-right": 61470, "fa-angle-double-down": 61699, "fa-facebook": 61594, "fa-hand-scissors-o": 62039, "fa-scribd": 62090, "fa-sort": 61660, "fa-twitter-square": 61569, "fa-power-off": 61457, "fa-gratipay": 61828, "fa-mars": 61986, "fa-header": 61916, "fa-user": 61447, "fa-exclamation-circle": 61546, "fa-level-down": 61769, "fa-vine": 61898, "fa-tint": 61507, "fa-wordpress": 61850, "fa-expeditedssl": 62014, "fa-slack": 61848, "fa-cut": 61636, "fa-key": 61572, "fa-tripadvisor": 62050, "fa-opera": 62058, "fa-object-group": 62023, "fa-heartbeat": 61982, "fa-laptop": 61705, "fa-bomb": 61922, "fa-angle-double-left": 61696, "fa-quote-left": 61709, "fa-paw": 61872, "fa-reddit": 61857, "fa-cc-visa": 61936, "fa-circle-o": 61708, "fa-odnoklassniki-square": 62052, "fa-cc-jcb": 62027, "fa-mail-reply-all": 61730, "fa-lastfm": 61954, "fa-group": 61632, "fa-microphone": 61744, "fa-camera-retro": 61571, "fa-maxcdn": 61750, "fa-buysellads": 61965, "fa-play-circle-o": 61469, "fa-ge": 61905, "fa-i-cursor": 62022, "fa-percent": 62101, "fa-photo": 61502, "fa-toggle-on": 61957, "fa-fax": 61868, "fa-code-fork": 61734, "fa-y-combinator": 62011, "fa-map": 62073, "fa-try": 61845, "fa-diamond": 61977, "fa-neuter": 61996, "fa-quote-right": 61710, "fa-mobile": 61707, "fa-bell-slash": 61942, "fa-trademark": 62044, "fa-file-video-o": 61896, "fa-mixcloud": 62089, "fa-plus-circle": 61525, "fa-folder-open": 61564, "fa-css3": 61756, "fa-fast-forward": 61520, "fa-toggle-down": 61776, "fa-credit-card": 61597, "fa-caret-square-o-left": 61841, "fa-hourglass-3": 62035, "fa-hourglass-2": 62034, "fa-hourglass-1": 62033, "fa-angle-down": 61703, "fa-edge": 62082, "fa-trello": 61825, "fa-train": 62008, "fa-sheqel": 61963, "fa-file-powerpoint-o": 61892, "fa-arrow-left": 61536, "fa-television": 62060, "fa-life-saver": 61901, "fa-copy": 61637, "fa-sticky-note-o": 62026, "fa-mars-double": 61991, "fa-star-half-o": 61731, "fa-black-tie": 62078, "fa-chevron-up": 61559, "fa-chevron-down": 61560, "fa-fonticons": 62080, "fa-check-circle-o": 61533, "fa-plug": 61926, "fa-deviantart": 61885, "fa-dashboard": 61668, "fa-hourglass-o": 62032, "fa-plus": 61543, "fa-cc-discover": 61938, "fa-hashtag": 62098, "fa-gamepad": 61723, "fa-rub": 61784, "fa-history": 61914, "fa-sign-in": 61584, "fa-sort-amount-desc": 61793, "fa-rss-square": 61763, "fa-transgender": 61988, "fa-graduation-cap": 61853, "fa-whatsapp": 62002, "fa-mercury": 61987, "fa-amazon": 62064, "fa-medkit": 61690, "fa-bug": 61832, "fa-twitch": 61928, "fa-file-archive-o": 61894, "fa-forumbee": 61969, "fa-cny": 61783, "fa-arrows": 61511, "fa-map-marker": 61505, "fa-wheelchair": 61843, "fa-plus-square": 61694, "fa-male": 61827, "fa-institution": 61852, "fa-envelope-o": 61443, "fa-xing-square": 61801, "fa-step-forward": 61521, "fa-stumbleupon-circle": 61859, "fa-pencil-square-o": 61508, "fa-weibo": 61834, "fa-gear": 61459, "fa-rocket": 61749, "fa-bluetooth": 62099, "fa-search-plus": 61454, "fa-stop-circle": 62093, "fa-bell": 61683, "fa-undo": 61666, "fa-fast-backward": 61513, "fa-sliders": 61918, "fa-hotel": 62006, "fa-steam": 61878, "fa-hand-paper-o": 62038, "fa-circle-thin": 61915, "fa-share-square": 61773, "fa-asterisk": 61545, "fa-arrow-down": 61539, "fa-random": 61556, "fa-share-alt": 61920, "fa-beer": 61692, "fa-exclamation-triangle": 61553, "fa-commenting": 62074, "fa-volume-up": 61480, "fa-flag-checkered": 61726, "fa-ellipsis-h": 61761, "fa-hand-spock-o": 62041, "fa-crop": 61733, "fa-paragraph": 61917, "fa-battery-3": 62017, "fa-ellipsis-v": 61762, "fa-gift": 61547, "fa-strikethrough": 61644, "fa-motorcycle": 61980, "fa-life-bouy": 61901, "fa-reply-all": 61730, "fa-paper-plane-o": 61913, "fa-star-half": 61577, "fa-download": 61465, "fa-usb": 62087, "fa-chevron-circle-down": 61754, "fa-calculator": 61932, "fa-gg": 62048, "fa-contao": 62061, "fa-hand-o-down": 61607, "fa-leanpub": 61970, "fa-star-o": 61446, "fa-pie-chart": 61952, "fa-venus": 61985, "fa-inr": 61782, "fa-rupee": 61782, "fa-eur": 61779, "fa-tumblr": 61811, "fa-indent": 61500, "fa-mars-stroke-v": 61994, "fa-git": 61907, "fa-envelope": 61664, "fa-bitbucket-square": 61810, "fa-legal": 61667, "fa-gittip": 61828, "fa-chevron-left": 61523, "fa-cogs": 61573, "fa-arrow-circle-o-left": 61840, "fa-briefcase": 61617, "fa-user-md": 61680, "fa-angle-left": 61700, "fa-yc": 62011, "fa-long-arrow-right": 61816, "fa-coffee": 61684, "fa-copyright": 61945, "fa-toggle-up": 61777, "fa-support": 61901, "fa-youtube-square": 61798, "fa-cc-mastercard": 61937, "fa-unsorted": 61660, "fa-compress": 61542, "fa-android": 61819, "fa-font": 61489, "fa-arrow-right": 61537, "fa-minus": 61544, "fa-bitbucket": 61809, "fa-facebook-f": 61594, "fa-subway": 62009, "fa-headphones": 61477, "fa-paperclip": 61638, "fa-industry": 62069, "fa-rmb": 61783, "fa-minus-square": 61766, "fa-moon-o": 61830, "fa-file-excel-o": 61891, "fa-line-chart": 61953, "fa-fighter-jet": 61691, "fa-sort-alpha-desc": 61790, "fa-spotify": 61884, "fa-star-half-empty": 61731, "fa-share": 61540, "fa-comment": 61557, "fa-mars-stroke": 61993, "fa-stack-exchange": 61837, "fa-pied-piper": 61863, "fa-building": 61869, "fa-thumbs-up": 61796, "fa-chevron-circle-right": 61752, "fa-adjust": 61506, "fa-sellsy": 61971, "fa-paypal": 61933, "fa-signal": 61458, "fa-sort-up": 61662, "fa-shekel": 61963, "fa-codiepie": 62084, "fa-calendar-plus-o": 62065, "fa-digg": 61862, "fa-save": 61639, "fa-shopping-bag": 62096, "fa-eye-slash": 61552, "fa-backward": 61514, "fa-hand-stop-o": 62038, "fa-mail-forward": 61540, "fa-link": 61633, "fa-table": 61646, "fa-tag": 61483, "fa-turkish-lira": 61845, "fa-envelope-square": 61849, "fa-optin-monster": 62012, "fa-money": 61654, "fa-instagram": 61805, "fa-volume-off": 61478, "fa-unlock-alt": 61758, "fa-minus-circle": 61526, "fa-hacker-news": 61908, "fa-hand-grab-o": 62037, "fa-adn": 61808, "fa-list-ol": 61643, "fa-magnet": 61558, "fa-calendar-minus-o": 62066, "fa-linkedin": 61665, "fa-paper-plane": 61912, "fa-mouse-pointer": 62021, "fa-reply": 61714, "fa-smile-o": 61720, "fa-hourglass-half": 62034, "fa-behance-square": 61877, "fa-twitter": 61593, "fa-expand": 61541, "fa-flask": 61635, "fa-flash": 61671, "fa-trophy": 61585, "fa-long-arrow-down": 61813, "fa-odnoklassniki": 62051, "fa-angle-double-right": 61697, "fa-home": 61461, "fa-bolt": 61671, "fa-italic": 61491, "fa-comments": 61574, "fa-commenting-o": 62075, "fa-toggle-right": 61778, "fa-file": 61787, "fa-bold": 61490, "fa-internet-explorer": 62059, "fa-cc-amex": 61939, "fa-sort-down": 61661, "fa-anchor": 61757, "fa-medium": 62010, "fa-calendar": 61555, "fa-superscript": 61739, "fa-wechat": 61911, "fa-file-text-o": 61686, "fa-cloud": 61634, "fa-user-plus": 62004, "fa-times": 61453, "fa-street-view": 61981, "fa-trash": 61944, "fa-paste": 61674, "fa-ambulance": 61689, "fa-suitcase": 61682, "fa-binoculars": 61925, "fa-user-secret": 61979, "fa-sort-alpha-asc": 61789, "fa-picture-o": 61502, "fa-cc": 61962, "fa-calendar-times-o": 62067, "fa-phone": 61589, "fa-github-square": 61586, "fa-hand-peace-o": 62043, "fa-windows": 61818, "fa-500px": 62062, "fa-calendar-check-o": 62068, "fa-clock-o": 61463, "fa-connectdevelop": 61966, "fa-text-height": 61492, "fa-houzz": 62076, "fa-align-right": 61496, "fa-angle-right": 61701, "fa-hand-rock-o": 62037, "fa-heart-o": 61578, "fa-steam-square": 61879, "fa-underline": 61645, "fa-file-image-o": 61893, "fa-bus": 61959, "fa-play-circle": 61764, "fa-plus-square-o": 61846, "fa-rss": 61598, "fa-battery-0": 62020, "fa-battery-1": 62019, "fa-battery-2": 62018, "fa-google-plus-square": 61652, "fa-battery-4": 62016, "fa-caret-square-o-right": 61778, "fa-child": 61870, "fa-space-shuttle": 61847, "fa-pinterest-p": 62001, "fa-outdent": 61499, "fa-lock": 61475, "fa-dot-circle-o": 61842, "fa-git-square": 61906, "fa-clipboard": 61674, "fa-mortar-board": 61853, "fa-university": 61852, "fa-github": 61595, "fa-jpy": 61783, "fa-vk": 61833, "fa-print": 61487, "fa-code": 61729, "fa-book": 61485, "fa-pinterest": 61650, "fa-youtube": 61799, "fa-fire": 61549, "fa-hourglass-end": 62035, "fa-tasks": 61614, "fa-xing": 61800, "fa-ioxhost": 61960, "fa-play": 61515, "fa-flag-o": 61725, "fa-battery-half": 62018, "fa-search": 61442, "fa-genderless": 61997, "fa-renren": 61835, "fa-database": 61888, "fa-plane": 61554, "fa-sort-numeric-desc": 61795, "fa-intersex": 61988, "fa-tree": 61883, "fa-scissors": 61636, "fa-question-circle": 61529, "fa-close": 61453, "fa-crosshairs": 61531, "fa-apple": 61817, "fa-wrench": 61613, "fa-sitemap": 61672, "fa-language": 61867, "fa-automobile": 61881, "fa-hourglass": 62036, "fa-bar-chart": 61568, "fa-file-o": 61462, "fa-krw": 61785, "fa-soundcloud": 61886, "fa-floppy-o": 61639, "fa-upload": 61587, "fa-arrow-circle-o-right": 61838, "fa-info-circle": 61530, "fa-cloud-upload": 61678, "fa-facebook-official": 62000, "fa-search-minus": 61456, "fa-music": 61441, "fa-stumbleupon": 61860, "fa-star-half-full": 61731, "fa-file-picture-o": 61893, "fa-image": 61502, "fa-mobile-phone": 61707, "fa-dollar": 61781, "fa-google-wallet": 61934, "fa-feed": 61598, "fa-vimeo": 62077, "fa-futbol-o": 61923, "fa-hdd-o": 61600, "fa-remove": 61453, "fa-bullseye": 61760, "fa-location-arrow": 61732, "fa-female": 61826, "fa-joomla": 61866, "fa-thumb-tack": 61581, "fa-align-justify": 61497, "fa-external-link": 61582, "fa-arrow-circle-right": 61609, "fa-level-up": 61768, "fa-gears": 61573, "fa-foursquare": 61824, "fa-venus-mars": 61992, "fa-yelp": 61929, "fa-exclamation": 61738, "fa-star": 61445, "fa-google-plus": 61653, "fa-ra": 61904, "fa-h-square": 61693, "fa-lastfm-square": 61955, "fa-registered": 62045, "fa-edit": 61508, "fa-unlock": 61596, "fa-sort-desc": 61661, "fa-tencent-weibo": 61909, "fa-thumbs-down": 61797, "fa-eject": 61522, "fa-linkedin-square": 61580, "fa-pagelines": 61836, "fa-chain": 61633, "fa-yahoo": 61854, "fa-send": 61912, "fa-check": 61452, "fa-compass": 61774, "fa-viacoin": 62007, "fa-angle-up": 61702, "fa-wikipedia-w": 62054, "fa-qrcode": 61481, "fa-paint-brush": 61948, "fa-bookmark-o": 61591, "fa-usd": 61781, "fa-chevron-right": 61524, "fa-ruble": 61784, "fa-delicious": 61861, "fa-btc": 61786, "fa-lemon-o": 61588, "fa-arrow-circle-up": 61610, "fa-comments-o": 61670, "fa-chrome": 62056, "fa-check-square-o": 61510, "fa-ils": 61963, "fa-birthday-cake": 61949, "fa-tablet": 61706, "fa-codepen": 61899, "fa-stop-circle-o": 62094, "fa-chain-broken": 61735, "fa-puzzle-piece": 61742, "fa-creative-commons": 62046, "fa-spinner": 61712, "fa-newspaper-o": 61930, "fa-globe": 61612, "fa-firefox": 62057, "fa-vimeo-square": 61844, "fa-magic": 61648, "fa-align-center": 61495, "fa-warning": 61553, "fa-desktop": 61704, "fa-cc-diners-club": 62028, "fa-thumbs-o-up": 61575, "fa-dribbble": 61821, "fa-square-o": 61590, "fa-columns": 61659, "fa-flickr": 61806, "fa-retweet": 61561, "fa-flag": 61476, "fa-google": 61856, "fa-file-sound-o": 61895, "fa-bitcoin": 61786, "fa-text-width": 61493, "fa-arrows-v": 61565, "fa-hospital-o": 61688, "fa-step-backward": 61512, "fa-bullhorn": 61601, "fa-fire-extinguisher": 61748, "fa-arrows-h": 61566, "fa-refresh": 61473, "fa-fort-awesome": 62086, "fa-github-alt": 61715, "fa-reorder": 61641, "fa-modx": 62085, "fa-facebook-square": 61570, "fa-empire": 61905, "fa-credit-card-alt": 62083, "fa-qq": 61910, "fa-rouble": 61784, "fa-minus-square-o": 61767, "fa-rotate-left": 61666, "fa-file-pdf-o": 61889}
================================================
FILE: kivystudio/settings.py
================================================
import os
from kivy.event import EventDispatcher
from kivy.config import ConfigParser
from kivy.properties import ConfigParserProperty
from kivystudio.tools import get_user_data_dir
from kivy.core.window import Window
Window.maximize()
config = ConfigParser('kivystudio')
config.adddefaultsection('application')
config.adddefaultsection('graphics')
config_file = os.path.join(get_user_data_dir('kivystudio'), 'config.ini')
config.read(config_file)
class SettingDispatcher(EventDispatcher):
auto_save = ConfigParserProperty(0, 'application', 'auto_save', 'kivystudio',val_type=int)
auto_emulate = ConfigParserProperty(1, 'application', 'auto_emulate', 'kivystudio',val_type=int)
dpi_scale = ConfigParserProperty(1, 'graphics', 'dpi_scale', 'kivystudio',val_type=float)
settings_obj = SettingDispatcher()
================================================
FILE: kivystudio/tools/__init__.py
================================================
import os
from os.path import dirname, join, exists, expanduser
from kivy import platform
from kivy.lang import Builder
from kivy.core.window import Window
def set_auto_mouse_position(widget):
''' functions trys to position widget
automaticaly on the mouse pos'''
if Window.mouse_pos[0]+widget.width > Window.width:
widget.x = Window.mouse_pos[0]
else:
widget.x = Window.mouse_pos[0]
if (Window.mouse_pos[1]+widget.height > Window.height):
widget.top = Window.mouse_pos[1]-16
else:
widget.top = Window.mouse_pos[1]-16
def load_kv(filepath, file):
''' load a kivy file from the current
directory of the file calling this func
where filepath is __file__ and file is a kv file'''
filepath = dirname(filepath)
Builder.load_file(join(filepath, file))
def get_user_data_dir(name):
# Determine and return the user_data_dir.
data_dir = ""
if platform == 'ios':
raise NotImplemented()
elif platform == 'android':
raise NotImplemented()
elif platform == 'win':
data_dir = os.path.join(os.environ['APPDATA'], name)
elif platform == 'macosx':
data_dir = '~/Library/Application Support/{}'.format(name)
data_dir = expanduser(data_dir)
else: # _platform == 'linux' or anything else...:
data_dir = os.environ.get('XDG_CONFIG_HOME', '~/.config')
data_dir = expanduser(join(data_dir, name))
if not exists(data_dir):
os.mkdir(data_dir)
return data_dir
================================================
FILE: kivystudio/tools/iconfonts/LICENSE
================================================
Copyright (c) 2010-2015 Kivy Team and other contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
================================================
FILE: kivystudio/tools/iconfonts/README.md
================================================

Kivy-iconfonts
==============
Simple helper functions to make easier to use icon fonts in Labels and derived widgets
Usage
=====
Once you have a .fontd file (see below) for your ttf iconfont generated you can use it like this:
In your main.py register your font:
```python
iconfonts.register('default_font', 'iconfont_sample.ttf', 'iconfont_sample.fontd')
```
In your kv file or string:
```yaml
#: import icon kivy.garden.iconfonts.icon
Button:
markup: True # Always turn markup on
text: "%s"%(icon('icon-comment'))
```
See __init__.py for another example.
Generating a fontd file
=====================
A .fontd file is just a python dictionary filled with icon_code: unicode_value entries. This information is extracted from a css file (all iconfonts packages I've seen have one).
**Example with Font-Awesome**
1. Download Font-Awesome (http://fortawesome.github.io/Font-Awesome/)
2. Copy both the TTF and CSS files (fonts/fontawesome-webfont.ttf and css/font-awesome.css) to your project
3. Create and execute a python script to generate your fontd file:
```python
iconfonts.create_fontdict_file('font-awesome.css', 'font-awesome.fontd')
```
4. If everything went well your font dictionary file exists. You can delete the css file (font-awesome.css)
More IconFonts
==============
- http://fortawesome.github.io/Font-Awesome/
- http://fontello.com/
- https://icomoon.io
LICENSE
=======
MIT (except sample font that I got from http://fontello.com)
Credits
=======
Author: Jeyson Molina
================================================
FILE: kivystudio/tools/iconfonts/__init__.py
================================================
"""
Kivy-iconfonts
==============
Simple helper functions to make easier to use icon fonts in Labels and derived
widgets.
"""
from .iconfonts import *
if __name__ == '__main__':
from kivy.lang import Builder
from kivy.base import runTouchApp
from kivy.animation import Animation
from os.path import join, dirname
kv = """
#: import icon iconfonts.icon
BoxLayout:
Button:
markup: True
text: "%s"%(icon('icon-comment', 32))
Button:
markup: True
text: "%s"%(icon('icon-emo-happy', 64))
Button:
markup: True
text: "%s Text"%(icon('icon-plus-circled', 24))
Button:
markup: True
text: "%s"%(icon('icon-doc-text-inv', 64, 'ff3333'))
Label:
id: _anim
markup: True
text: "%s"%(icon('icon-spin6', 32))
font_color: 1, 0, 0, 1
p: 0
canvas:
Clear
PushMatrix
Rotate:
angle: -self.p
origin: self.center_x , self.center_y
Rectangle:
size: (32, 32)
pos: self.center_x - 16, self.center_y - 16
texture: self.texture
PopMatrix
"""
register('default_font', 'iconfont_sample.ttf',
join(dirname(__file__), 'iconfont_sample.fontd'))
root = Builder.load_string(kv)
an = Animation(p=360, duration=2) + Animation(p=0, duration=0)
an.repeat = True
an.start(root.ids['_anim'])
runTouchApp(root)
================================================
FILE: kivystudio/tools/iconfonts/iconfonts.py
================================================
import re
import json
from collections import OrderedDict
from kivy.compat import PY2
_register = OrderedDict()
if not PY2:
unichr = chr
def register(name, ttf_fname, fontd_fname):
"""Register an Iconfont
:param name: font name identifier.
:param ttf_fname: ttf filename (path)
:param fontd_fname: fontdic filename. (See create_fontdic)
"""
with open(fontd_fname, 'r') as f:
fontd = json.loads(f.read())
_register[name] = ttf_fname, fontd_fname, fontd
def icon(code, size=None, color=None, font_name=None):
""" Gets an icon from iconfont.
:param code: Icon codename (ex: 'icon-name')
:param size: Icon size
:param color: Icon color
:param font_name: Registered font name. If None first one is used.
:returns: icon text (with markups)
"""
font = list(_register.keys())[0] if font_name is None else font_name
font_data = _register[font]
s = "[font=%s]%s[/font]" % (font_data[0], unichr(font_data[2][code]))
if size is not None:
s = "[size=%s]%s[/size]" % (size, s)
if color is not None:
s = "[color=%s]%s[/color]" % (color, s)
return s
def create_fontdict_file(css_fname, output_fname):
"""Creates a font dictionary file. Basically creates a dictionary filled
with icon_code: unicode_value entries
obtained from a CSS file.
:param css_fname: CSS filename where font's rules are declared.
:param output_fname: Fontd file destination
"""
with open(css_fname, 'r') as f:
data = f.read()
res = _parse(data)
with open(output_fname, 'w') as o:
o.write(json.dumps(res))
return res
def _parse(data):
# find start index where icons rules start
pat_start = re.compile('}.+content:', re.DOTALL)
rules_start = [x for x in re.finditer(pat_start, data)][0].start()
data = data[rules_start:] # crop data
data = data.replace("\\", '0x') # replace unicodes
data = data.replace("'", '"') # replace quotes
# iterate rule indices and extract value
pat_keys = re.compile('[a-zA-Z0-9_-]+:before')
res = dict()
for i in re.finditer(pat_keys, data):
start = i.start()
end = data.find('}', start)
key = i.group().replace(':before', '')
try:
value = int(data[start:end].split('"')[1], 0)
except (IndexError, ValueError):
continue
res[key] = value
return res
================================================
FILE: kivystudio/tools/iconfonts/test/font-awesome.css
================================================
/*!
* Font Awesome 4.5.0 by @davegandy - http://fontawesome.io - @fontawesome
* License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License)
*/@font-face{font-family:'FontAwesome';src:url('../fonts/fontawesome-webfont.eot?v=4.5.0');src:url('../fonts/fontawesome-webfont.eot?#iefix&v=4.5.0') format('embedded-opentype'),url('../fonts/fontawesome-webfont.woff2?v=4.5.0') format('woff2'),url('../fonts/fontawesome-webfont.woff?v=4.5.0') format('woff'),url('../fonts/fontawesome-webfont.ttf?v=4.5.0') format('truetype'),url('../fonts/fontawesome-webfont.svg?v=4.5.0#fontawesomeregular') format('svg');font-weight:normal;font-style:normal}.fa{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571429em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14285714em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14285714em;width:2.14285714em;top:.14285714em;text-align:center}.fa-li.fa-lg{left:-1.85714286em}.fa-border{padding:.2em .25em .15em;border:solid .08em #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left{margin-right:.3em}.fa.fa-pull-right{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left{margin-right:.3em}.fa.pull-right{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s infinite linear;animation:fa-spin 2s infinite linear}.fa-pulse{-webkit-animation:fa-spin 1s infinite steps(8);animation:fa-spin 1s infinite steps(8)}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=1);-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2);-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=3);-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1);-webkit-transform:scale(-1, 1);-ms-transform:scale(-1, 1);transform:scale(-1, 1)}.fa-flip-vertical{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1);-webkit-transform:scale(1, -1);-ms-transform:scale(1, -1);transform:scale(1, -1)}:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270,:root .fa-flip-horizontal,:root .fa-flip-vertical{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:"\f000"}.fa-music:before{content:"\f001"}.fa-search:before{content:"\f002"}.fa-envelope-o:before{content:"\f003"}.fa-heart:before{content:"\f004"}.fa-star:before{content:"\f005"}.fa-star-o:before{content:"\f006"}.fa-user:before{content:"\f007"}.fa-film:before{content:"\f008"}.fa-th-large:before{content:"\f009"}.fa-th:before{content:"\f00a"}.fa-th-list:before{content:"\f00b"}.fa-check:before{content:"\f00c"}.fa-remove:before,.fa-close:before,.fa-times:before{content:"\f00d"}.fa-search-plus:before{content:"\f00e"}.fa-search-minus:before{content:"\f010"}.fa-power-off:before{content:"\f011"}.fa-signal:before{content:"\f012"}.fa-gear:before,.fa-cog:before{content:"\f013"}.fa-trash-o:before{content:"\f014"}.fa-home:before{content:"\f015"}.fa-file-o:before{content:"\f016"}.fa-clock-o:before{content:"\f017"}.fa-road:before{content:"\f018"}.fa-download:before{content:"\f019"}.fa-arrow-circle-o-down:before{content:"\f01a"}.fa-arrow-circle-o-up:before{content:"\f01b"}.fa-inbox:before{content:"\f01c"}.fa-play-circle-o:before{content:"\f01d"}.fa-rotate-right:before,.fa-repeat:before{content:"\f01e"}.fa-refresh:before{content:"\f021"}.fa-list-alt:before{content:"\f022"}.fa-lock:before{content:"\f023"}.fa-flag:before{content:"\f024"}.fa-headphones:before{content:"\f025"}.fa-volume-off:before{content:"\f026"}.fa-volume-down:before{content:"\f027"}.fa-volume-up:before{content:"\f028"}.fa-qrcode:before{content:"\f029"}.fa-barcode:before{content:"\f02a"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-book:before{content:"\f02d"}.fa-bookmark:before{content:"\f02e"}.fa-print:before{content:"\f02f"}.fa-camera:before{content:"\f030"}.fa-font:before{content:"\f031"}.fa-bold:before{content:"\f032"}.fa-italic:before{content:"\f033"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-align-left:before{content:"\f036"}.fa-align-center:before{content:"\f037"}.fa-align-right:before{content:"\f038"}.fa-align-justify:before{content:"\f039"}.fa-list:before{content:"\f03a"}.fa-dedent:before,.fa-outdent:before{content:"\f03b"}.fa-indent:before{content:"\f03c"}.fa-video-camera:before{content:"\f03d"}.fa-photo:before,.fa-image:before,.fa-picture-o:before{content:"\f03e"}.fa-pencil:before{content:"\f040"}.fa-map-marker:before{content:"\f041"}.fa-adjust:before{content:"\f042"}.fa-tint:before{content:"\f043"}.fa-edit:before,.fa-pencil-square-o:before{content:"\f044"}.fa-share-square-o:before{content:"\f045"}.fa-check-square-o:before{content:"\f046"}.fa-arrows:before{content:"\f047"}.fa-step-backward:before{content:"\f048"}.fa-fast-backward:before{content:"\f049"}.fa-backward:before{content:"\f04a"}.fa-play:before{content:"\f04b"}.fa-pause:before{content:"\f04c"}.fa-stop:before{content:"\f04d"}.fa-forward:before{content:"\f04e"}.fa-fast-forward:before{content:"\f050"}.fa-step-forward:before{content:"\f051"}.fa-eject:before{content:"\f052"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-plus-circle:before{content:"\f055"}.fa-minus-circle:before{content:"\f056"}.fa-times-circle:before{content:"\f057"}.fa-check-circle:before{content:"\f058"}.fa-question-circle:before{content:"\f059"}.fa-info-circle:before{content:"\f05a"}.fa-crosshairs:before{content:"\f05b"}.fa-times-circle-o:before{content:"\f05c"}.fa-check-circle-o:before{content:"\f05d"}.fa-ban:before{content:"\f05e"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrow-down:before{content:"\f063"}.fa-mail-forward:before,.fa-share:before{content:"\f064"}.fa-expand:before{content:"\f065"}.fa-compress:before{content:"\f066"}.fa-plus:before{content:"\f067"}.fa-minus:before{content:"\f068"}.fa-asterisk:before{content:"\f069"}.fa-exclamation-circle:before{content:"\f06a"}.fa-gift:before{content:"\f06b"}.fa-leaf:before{content:"\f06c"}.fa-fire:before{content:"\f06d"}.fa-eye:before{content:"\f06e"}.fa-eye-slash:before{content:"\f070"}.fa-warning:before,.fa-exclamation-triangle:before{content:"\f071"}.fa-plane:before{content:"\f072"}.fa-calendar:before{content:"\f073"}.fa-random:before{content:"\f074"}.fa-comment:before{content:"\f075"}.fa-magnet:before{content:"\f076"}.fa-chevron-up:before{content:"\f077"}.fa-chevron-down:before{content:"\f078"}.fa-retweet:before{content:"\f079"}.fa-shopping-cart:before{content:"\f07a"}.fa-folder:before{content:"\f07b"}.fa-folder-open:before{content:"\f07c"}.fa-arrows-v:before{content:"\f07d"}.fa-arrows-h:before{content:"\f07e"}.fa-bar-chart-o:before,.fa-bar-chart:before{content:"\f080"}.fa-twitter-square:before{content:"\f081"}.fa-facebook-square:before{content:"\f082"}.fa-camera-retro:before{content:"\f083"}.fa-key:before{content:"\f084"}.fa-gears:before,.fa-cogs:before{content:"\f085"}.fa-comments:before{content:"\f086"}.fa-thumbs-o-up:before{content:"\f087"}.fa-thumbs-o-down:before{content:"\f088"}.fa-star-half:before{content:"\f089"}.fa-heart-o:before{content:"\f08a"}.fa-sign-out:before{content:"\f08b"}.fa-linkedin-square:before{content:"\f08c"}.fa-thumb-tack:before{content:"\f08d"}.fa-external-link:before{content:"\f08e"}.fa-sign-in:before{content:"\f090"}.fa-trophy:before{content:"\f091"}.fa-github-square:before{content:"\f092"}.fa-upload:before{content:"\f093"}.fa-lemon-o:before{content:"\f094"}.fa-phone:before{content:"\f095"}.fa-square-o:before{content:"\f096"}.fa-bookmark-o:before{content:"\f097"}.fa-phone-square:before{content:"\f098"}.fa-twitter:before{content:"\f099"}.fa-facebook-f:before,.fa-facebook:before{content:"\f09a"}.fa-github:before{content:"\f09b"}.fa-unlock:before{content:"\f09c"}.fa-credit-card:before{content:"\f09d"}.fa-feed:before,.fa-rss:before{content:"\f09e"}.fa-hdd-o:before{content:"\f0a0"}.fa-bullhorn:before{content:"\f0a1"}.fa-bell:before{content:"\f0f3"}.fa-certificate:before{content:"\f0a3"}.fa-hand-o-right:before{content:"\f0a4"}.fa-hand-o-left:before{content:"\f0a5"}.fa-hand-o-up:before{content:"\f0a6"}.fa-hand-o-down:before{content:"\f0a7"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-globe:before{content:"\f0ac"}.fa-wrench:before{content:"\f0ad"}.fa-tasks:before{content:"\f0ae"}.fa-filter:before{content:"\f0b0"}.fa-briefcase:before{content:"\f0b1"}.fa-arrows-alt:before{content:"\f0b2"}.fa-group:before,.fa-users:before{content:"\f0c0"}.fa-chain:before,.fa-link:before{content:"\f0c1"}.fa-cloud:before{content:"\f0c2"}.fa-flask:before{content:"\f0c3"}.fa-cut:before,.fa-scissors:before{content:"\f0c4"}.fa-copy:before,.fa-files-o:before{content:"\f0c5"}.fa-paperclip:before{content:"\f0c6"}.fa-save:before,.fa-floppy-o:before{content:"\f0c7"}.fa-square:before{content:"\f0c8"}.fa-navicon:before,.fa-reorder:before,.fa-bars:before{content:"\f0c9"}.fa-list-ul:before{content:"\f0ca"}.fa-list-ol:before{content:"\f0cb"}.fa-strikethrough:before{content:"\f0cc"}.fa-underline:before{content:"\f0cd"}.fa-table:before{content:"\f0ce"}.fa-magic:before{content:"\f0d0"}.fa-truck:before{content:"\f0d1"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-square:before{content:"\f0d3"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-plus:before{content:"\f0d5"}.fa-money:before{content:"\f0d6"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-up:before{content:"\f0d8"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-columns:before{content:"\f0db"}.fa-unsorted:before,.fa-sort:before{content:"\f0dc"}.fa-sort-down:before,.fa-sort-desc:before{content:"\f0dd"}.fa-sort-up:before,.fa-sort-asc:before{content:"\f0de"}.fa-envelope:before{content:"\f0e0"}.fa-linkedin:before{content:"\f0e1"}.fa-rotate-left:before,.fa-undo:before{content:"\f0e2"}.fa-legal:before,.fa-gavel:before{content:"\f0e3"}.fa-dashboard:before,.fa-tachometer:before{content:"\f0e4"}.fa-comment-o:before{content:"\f0e5"}.fa-comments-o:before{content:"\f0e6"}.fa-flash:before,.fa-bolt:before{content:"\f0e7"}.fa-sitemap:before{content:"\f0e8"}.fa-umbrella:before{content:"\f0e9"}.fa-paste:before,.fa-clipboard:before{content:"\f0ea"}.fa-lightbulb-o:before{content:"\f0eb"}.fa-exchange:before{content:"\f0ec"}.fa-cloud-download:before{content:"\f0ed"}.fa-cloud-upload:before{content:"\f0ee"}.fa-user-md:before{content:"\f0f0"}.fa-stethoscope:before{content:"\f0f1"}.fa-suitcase:before{content:"\f0f2"}.fa-bell-o:before{content:"\f0a2"}.fa-coffee:before{content:"\f0f4"}.fa-cutlery:before{content:"\f0f5"}.fa-file-text-o:before{content:"\f0f6"}.fa-building-o:before{content:"\f0f7"}.fa-hospital-o:before{content:"\f0f8"}.fa-ambulance:before{content:"\f0f9"}.fa-medkit:before{content:"\f0fa"}.fa-fighter-jet:before{content:"\f0fb"}.fa-beer:before{content:"\f0fc"}.fa-h-square:before{content:"\f0fd"}.fa-plus-square:before{content:"\f0fe"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angle-down:before{content:"\f107"}.fa-desktop:before{content:"\f108"}.fa-laptop:before{content:"\f109"}.fa-tablet:before{content:"\f10a"}.fa-mobile-phone:before,.fa-mobile:before{content:"\f10b"}.fa-circle-o:before{content:"\f10c"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-spinner:before{content:"\f110"}.fa-circle:before{content:"\f111"}.fa-mail-reply:before,.fa-reply:before{content:"\f112"}.fa-github-alt:before{content:"\f113"}.fa-folder-o:before{content:"\f114"}.fa-folder-open-o:before{content:"\f115"}.fa-smile-o:before{content:"\f118"}.fa-frown-o:before{content:"\f119"}.fa-meh-o:before{content:"\f11a"}.fa-gamepad:before{content:"\f11b"}.fa-keyboard-o:before{content:"\f11c"}.fa-flag-o:before{content:"\f11d"}.fa-flag-checkered:before{content:"\f11e"}.fa-terminal:before{content:"\f120"}.fa-code:before{content:"\f121"}.fa-mail-reply-all:before,.fa-reply-all:before{content:"\f122"}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:"\f123"}.fa-location-arrow:before{content:"\f124"}.fa-crop:before{content:"\f125"}.fa-code-fork:before{content:"\f126"}.fa-unlink:before,.fa-chain-broken:before{content:"\f127"}.fa-question:before{content:"\f128"}.fa-info:before{content:"\f129"}.fa-exclamation:before{content:"\f12a"}.fa-superscript:before{content:"\f12b"}.fa-subscript:before{content:"\f12c"}.fa-eraser:before{content:"\f12d"}.fa-puzzle-piece:before{content:"\f12e"}.fa-microphone:before{content:"\f130"}.fa-microphone-slash:before{content:"\f131"}.fa-shield:before{content:"\f132"}.fa-calendar-o:before{content:"\f133"}.fa-fire-extinguisher:before{content:"\f134"}.fa-rocket:before{content:"\f135"}.fa-maxcdn:before{content:"\f136"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-html5:before{content:"\f13b"}.fa-css3:before{content:"\f13c"}.fa-anchor:before{content:"\f13d"}.fa-unlock-alt:before{content:"\f13e"}.fa-bullseye:before{content:"\f140"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-rss-square:before{content:"\f143"}.fa-play-circle:before{content:"\f144"}.fa-ticket:before{content:"\f145"}.fa-minus-square:before{content:"\f146"}.fa-minus-square-o:before{content:"\f147"}.fa-level-up:before{content:"\f148"}.fa-level-down:before{content:"\f149"}.fa-check-square:before{content:"\f14a"}.fa-pencil-square:before{content:"\f14b"}.fa-external-link-square:before{content:"\f14c"}.fa-share-square:before{content:"\f14d"}.fa-compass:before{content:"\f14e"}.fa-toggle-down:before,.fa-caret-square-o-down:before{content:"\f150"}.fa-toggle-up:before,.fa-caret-square-o-up:before{content:"\f151"}.fa-toggle-right:before,.fa-caret-square-o-right:before{content:"\f152"}.fa-euro:before,.fa-eur:before{content:"\f153"}.fa-gbp:before{content:"\f154"}.fa-dollar:before,.fa-usd:before{content:"\f155"}.fa-rupee:before,.fa-inr:before{content:"\f156"}.fa-cny:before,.fa-rmb:before,.fa-yen:before,.fa-jpy:before{content:"\f157"}.fa-ruble:before,.fa-rouble:before,.fa-rub:before{content:"\f158"}.fa-won:before,.fa-krw:before{content:"\f159"}.fa-bitcoin:before,.fa-btc:before{content:"\f15a"}.fa-file:before{content:"\f15b"}.fa-file-text:before{content:"\f15c"}.fa-sort-alpha-asc:before{content:"\f15d"}.fa-sort-alpha-desc:before{content:"\f15e"}.fa-sort-amount-asc:before{content:"\f160"}.fa-sort-amount-desc:before{content:"\f161"}.fa-sort-numeric-asc:before{content:"\f162"}.fa-sort-numeric-desc:before{content:"\f163"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbs-down:before{content:"\f165"}.fa-youtube-square:before{content:"\f166"}.fa-youtube:before{content:"\f167"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-youtube-play:before{content:"\f16a"}.fa-dropbox:before{content:"\f16b"}.fa-stack-overflow:before{content:"\f16c"}.fa-instagram:before{content:"\f16d"}.fa-flickr:before{content:"\f16e"}.fa-adn:before{content:"\f170"}.fa-bitbucket:before{content:"\f171"}.fa-bitbucket-square:before{content:"\f172"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-long-arrow-down:before{content:"\f175"}.fa-long-arrow-up:before{content:"\f176"}.fa-long-arrow-left:before{content:"\f177"}.fa-long-arrow-right:before{content:"\f178"}.fa-apple:before{content:"\f179"}.fa-windows:before{content:"\f17a"}.fa-android:before{content:"\f17b"}.fa-linux:before{content:"\f17c"}.fa-dribbble:before{content:"\f17d"}.fa-skype:before{content:"\f17e"}.fa-foursquare:before{content:"\f180"}.fa-trello:before{content:"\f181"}.fa-female:before{content:"\f182"}.fa-male:before{content:"\f183"}.fa-gittip:before,.fa-gratipay:before{content:"\f184"}.fa-sun-o:before{content:"\f185"}.fa-moon-o:before{content:"\f186"}.fa-archive:before{content:"\f187"}.fa-bug:before{content:"\f188"}.fa-vk:before{content:"\f189"}.fa-weibo:before{content:"\f18a"}.fa-renren:before{content:"\f18b"}.fa-pagelines:before{content:"\f18c"}.fa-stack-exchange:before{content:"\f18d"}.fa-arrow-circle-o-right:before{content:"\f18e"}.fa-arrow-circle-o-left:before{content:"\f190"}.fa-toggle-left:before,.fa-caret-square-o-left:before{content:"\f191"}.fa-dot-circle-o:before{content:"\f192"}.fa-wheelchair:before{content:"\f193"}.fa-vimeo-square:before{content:"\f194"}.fa-turkish-lira:before,.fa-try:before{content:"\f195"}.fa-plus-square-o:before{content:"\f196"}.fa-space-shuttle:before{content:"\f197"}.fa-slack:before{content:"\f198"}.fa-envelope-square:before{content:"\f199"}.fa-wordpress:before{content:"\f19a"}.fa-openid:before{content:"\f19b"}.fa-institution:before,.fa-bank:before,.fa-university:before{content:"\f19c"}.fa-mortar-board:before,.fa-graduation-cap:before{content:"\f19d"}.fa-yahoo:before{content:"\f19e"}.fa-google:before{content:"\f1a0"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-square:before{content:"\f1a2"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-stumbleupon:before{content:"\f1a4"}.fa-delicious:before{content:"\f1a5"}.fa-digg:before{content:"\f1a6"}.fa-pied-piper:before{content:"\f1a7"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-drupal:before{content:"\f1a9"}.fa-joomla:before{content:"\f1aa"}.fa-language:before{content:"\f1ab"}.fa-fax:before{content:"\f1ac"}.fa-building:before{content:"\f1ad"}.fa-child:before{content:"\f1ae"}.fa-paw:before{content:"\f1b0"}.fa-spoon:before{content:"\f1b1"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-recycle:before{content:"\f1b8"}.fa-automobile:before,.fa-car:before{content:"\f1b9"}.fa-cab:before,.fa-taxi:before{content:"\f1ba"}.fa-tree:before{content:"\f1bb"}.fa-spotify:before{content:"\f1bc"}.fa-deviantart:before{content:"\f1bd"}.fa-soundcloud:before{content:"\f1be"}.fa-database:before{content:"\f1c0"}.fa-file-pdf-o:before{content:"\f1c1"}.fa-file-word-o:before{content:"\f1c2"}.fa-file-excel-o:before{content:"\f1c3"}.fa-file-powerpoint-o:before{content:"\f1c4"}.fa-file-photo-o:before,.fa-file-picture-o:before,.fa-file-image-o:before{content:"\f1c5"}.fa-file-zip-o:before,.fa-file-archive-o:before{content:"\f1c6"}.fa-file-sound-o:before,.fa-file-audio-o:before{content:"\f1c7"}.fa-file-movie-o:before,.fa-file-video-o:before{content:"\f1c8"}.fa-file-code-o:before{content:"\f1c9"}.fa-vine:before{content:"\f1ca"}.fa-codepen:before{content:"\f1cb"}.fa-jsfiddle:before{content:"\f1cc"}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-saver:before,.fa-support:before,.fa-life-ring:before{content:"\f1cd"}.fa-circle-o-notch:before{content:"\f1ce"}.fa-ra:before,.fa-rebel:before{content:"\f1d0"}.fa-ge:before,.fa-empire:before{content:"\f1d1"}.fa-git-square:before{content:"\f1d2"}.fa-git:before{content:"\f1d3"}.fa-y-combinator-square:before,.fa-yc-square:before,.fa-hacker-news:before{content:"\f1d4"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-qq:before{content:"\f1d6"}.fa-wechat:before,.fa-weixin:before{content:"\f1d7"}.fa-send:before,.fa-paper-plane:before{content:"\f1d8"}.fa-send-o:before,.fa-paper-plane-o:before{content:"\f1d9"}.fa-history:before{content:"\f1da"}.fa-circle-thin:before{content:"\f1db"}.fa-header:before{content:"\f1dc"}.fa-paragraph:before{content:"\f1dd"}.fa-sliders:before{content:"\f1de"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-bomb:before{content:"\f1e2"}.fa-soccer-ball-o:before,.fa-futbol-o:before{content:"\f1e3"}.fa-tty:before{content:"\f1e4"}.fa-binoculars:before{content:"\f1e5"}.fa-plug:before{content:"\f1e6"}.fa-slideshare:before{content:"\f1e7"}.fa-twitch:before{content:"\f1e8"}.fa-yelp:before{content:"\f1e9"}.fa-newspaper-o:before{content:"\f1ea"}.fa-wifi:before{content:"\f1eb"}.fa-calculator:before{content:"\f1ec"}.fa-paypal:before{content:"\f1ed"}.fa-google-wallet:before{content:"\f1ee"}.fa-cc-visa:before{content:"\f1f0"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-discover:before{content:"\f1f2"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-paypal:before{content:"\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-bell-slash:before{content:"\f1f6"}.fa-bell-slash-o:before{content:"\f1f7"}.fa-trash:before{content:"\f1f8"}.fa-copyright:before{content:"\f1f9"}.fa-at:before{content:"\f1fa"}.fa-eyedropper:before{content:"\f1fb"}.fa-paint-brush:before{content:"\f1fc"}.fa-birthday-cake:before{content:"\f1fd"}.fa-area-chart:before{content:"\f1fe"}.fa-pie-chart:before{content:"\f200"}.fa-line-chart:before{content:"\f201"}.fa-lastfm:before{content:"\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-toggle-off:before{content:"\f204"}.fa-toggle-on:before{content:"\f205"}.fa-bicycle:before{content:"\f206"}.fa-bus:before{content:"\f207"}.fa-ioxhost:before{content:"\f208"}.fa-angellist:before{content:"\f209"}.fa-cc:before{content:"\f20a"}.fa-shekel:before,.fa-sheqel:before,.fa-ils:before{content:"\f20b"}.fa-meanpath:before{content:"\f20c"}.fa-buysellads:before{content:"\f20d"}.fa-connectdevelop:before{content:"\f20e"}.fa-dashcube:before{content:"\f210"}.fa-forumbee:before{content:"\f211"}.fa-leanpub:before{content:"\f212"}.fa-sellsy:before{content:"\f213"}.fa-shirtsinbulk:before{content:"\f214"}.fa-simplybuilt:before{content:"\f215"}.fa-skyatlas:before{content:"\f216"}.fa-cart-plus:before{content:"\f217"}.fa-cart-arrow-down:before{content:"\f218"}.fa-diamond:before{content:"\f219"}.fa-ship:before{content:"\f21a"}.fa-user-secret:before{content:"\f21b"}.fa-motorcycle:before{content:"\f21c"}.fa-street-view:before{content:"\f21d"}.fa-heartbeat:before{content:"\f21e"}.fa-venus:before{content:"\f221"}.fa-mars:before{content:"\f222"}.fa-mercury:before{content:"\f223"}.fa-intersex:before,.fa-transgender:before{content:"\f224"}.fa-transgender-alt:before{content:"\f225"}.fa-venus-double:before{content:"\f226"}.fa-mars-double:before{content:"\f227"}.fa-venus-mars:before{content:"\f228"}.fa-mars-stroke:before{content:"\f229"}.fa-mars-stroke-v:before{content:"\f22a"}.fa-mars-stroke-h:before{content:"\f22b"}.fa-neuter:before{content:"\f22c"}.fa-genderless:before{content:"\f22d"}.fa-facebook-official:before{content:"\f230"}.fa-pinterest-p:before{content:"\f231"}.fa-whatsapp:before{content:"\f232"}.fa-server:before{content:"\f233"}.fa-user-plus:before{content:"\f234"}.fa-user-times:before{content:"\f235"}.fa-hotel:before,.fa-bed:before{content:"\f236"}.fa-viacoin:before{content:"\f237"}.fa-train:before{content:"\f238"}.fa-subway:before{content:"\f239"}.fa-medium:before{content:"\f23a"}.fa-yc:before,.fa-y-combinator:before{content:"\f23b"}.fa-optin-monster:before{content:"\f23c"}.fa-opencart:before{content:"\f23d"}.fa-expeditedssl:before{content:"\f23e"}.fa-battery-4:before,.fa-battery-full:before{content:"\f240"}.fa-battery-3:before,.fa-battery-three-quarters:before{content:"\f241"}.fa-battery-2:before,.fa-battery-half:before{content:"\f242"}.fa-battery-1:before,.fa-battery-quarter:before{content:"\f243"}.fa-battery-0:before,.fa-battery-empty:before{content:"\f244"}.fa-mouse-pointer:before{content:"\f245"}.fa-i-cursor:before{content:"\f246"}.fa-object-group:before{content:"\f247"}.fa-object-ungroup:before{content:"\f248"}.fa-sticky-note:before{content:"\f249"}.fa-sticky-note-o:before{content:"\f24a"}.fa-cc-jcb:before{content:"\f24b"}.fa-cc-diners-club:before{content:"\f24c"}.fa-clone:before{content:"\f24d"}.fa-balance-scale:before{content:"\f24e"}.fa-hourglass-o:before{content:"\f250"}.fa-hourglass-1:before,.fa-hourglass-start:before{content:"\f251"}.fa-hourglass-2:before,.fa-hourglass-half:before{content:"\f252"}.fa-hourglass-3:before,.fa-hourglass-end:before{content:"\f253"}.fa-hourglass:before{content:"\f254"}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:"\f255"}.fa-hand-stop-o:before,.fa-hand-paper-o:before{content:"\f256"}.fa-hand-scissors-o:before{content:"\f257"}.fa-hand-lizard-o:before{content:"\f258"}.fa-hand-spock-o:before{content:"\f259"}.fa-hand-pointer-o:before{content:"\f25a"}.fa-hand-peace-o:before{content:"\f25b"}.fa-trademark:before{content:"\f25c"}.fa-registered:before{content:"\f25d"}.fa-creative-commons:before{content:"\f25e"}.fa-gg:before{content:"\f260"}.fa-gg-circle:before{content:"\f261"}.fa-tripadvisor:before{content:"\f262"}.fa-odnoklassniki:before{content:"\f263"}.fa-odnoklassniki-square:before{content:"\f264"}.fa-get-pocket:before{content:"\f265"}.fa-wikipedia-w:before{content:"\f266"}.fa-safari:before{content:"\f267"}.fa-chrome:before{content:"\f268"}.fa-firefox:before{content:"\f269"}.fa-opera:before{content:"\f26a"}.fa-internet-explorer:before{content:"\f26b"}.fa-tv:before,.fa-television:before{content:"\f26c"}.fa-contao:before{content:"\f26d"}.fa-500px:before{content:"\f26e"}.fa-amazon:before{content:"\f270"}.fa-calendar-plus-o:before{content:"\f271"}.fa-calendar-minus-o:before{content:"\f272"}.fa-calendar-times-o:before{content:"\f273"}.fa-calendar-check-o:before{content:"\f274"}.fa-industry:before{content:"\f275"}.fa-map-pin:before{content:"\f276"}.fa-map-signs:before{content:"\f277"}.fa-map-o:before{content:"\f278"}.fa-map:before{content:"\f279"}.fa-commenting:before{content:"\f27a"}.fa-commenting-o:before{content:"\f27b"}.fa-houzz:before{content:"\f27c"}.fa-vimeo:before{content:"\f27d"}.fa-black-tie:before{content:"\f27e"}.fa-fonticons:before{content:"\f280"}.fa-reddit-alien:before{content:"\f281"}.fa-edge:before{content:"\f282"}.fa-credit-card-alt:before{content:"\f283"}.fa-codiepie:before{content:"\f284"}.fa-modx:before{content:"\f285"}.fa-fort-awesome:before{content:"\f286"}.fa-usb:before{content:"\f287"}.fa-product-hunt:before{content:"\f288"}.fa-mixcloud:before{content:"\f289"}.fa-scribd:before{content:"\f28a"}.fa-pause-circle:before{content:"\f28b"}.fa-pause-circle-o:before{content:"\f28c"}.fa-stop-circle:before{content:"\f28d"}.fa-stop-circle-o:before{content:"\f28e"}.fa-shopping-bag:before{content:"\f290"}.fa-shopping-basket:before{content:"\f291"}.fa-hashtag:before{content:"\f292"}.fa-bluetooth:before{content:"\f293"}.fa-bluetooth-b:before{content:"\f294"}.fa-percent:before{content:"\f295"}
================================================
FILE: kivystudio/tools/iconfonts/test/font-awesome.fontd
================================================
{"fa-camera": 61488, "fa-building-o": 61687, "fa-align-left": 61494, "fa-hand-o-up": 61606, "fa-external-link-square": 61772, "fa-cubes": 61875, "fa-get-pocket": 62053, "fa-share-alt-square": 61921, "fa-comment-o": 61669, "fa-thumbs-o-down": 61576, "fa-heart": 61444, "fa-file-photo-o": 61893, "fa-list": 61498, "fa-calendar-o": 61747, "fa-euro": 61779, "fa-life-buoy": 61901, "fa-stethoscope": 61681, "fa-bookmark": 61486, "fa-barcode": 61482, "fa-th-large": 61449, "fa-square": 61640, "fa-bank": 61852, "fa-times-circle-o": 61532, "fa-tachometer": 61668, "fa-bell-o": 61602, "fa-leaf": 61548, "fa-skype": 61822, "fa-certificate": 61603, "fa-car": 61881, "fa-ship": 61978, "fa-navicon": 61641, "fa-arrows-alt": 61618, "fa-server": 62003, "fa-cab": 61882, "fa-clone": 62029, "fa-wifi": 61931, "fa-meanpath": 61964, "fa-send-o": 61913, "fa-subscript": 61740, "fa-shirtsinbulk": 61972, "fa-sticky-note": 62025, "fa-road": 61464, "fa-times-circle": 61527, "fa-cart-arrow-down": 61976, "fa-safari": 62055, "fa-file-code-o": 61897, "fa-tty": 61924, "fa-list-ul": 61642, "fa-shopping-basket": 62097, "fa-arrow-circle-o-down": 61466, "fa-file-movie-o": 61896, "fa-slideshare": 61927, "fa-toggle-left": 61841, "fa-circle-o-notch": 61902, "fa-check-circle": 61528, "fa-mars-stroke-h": 61995, "fa-chevron-circle-up": 61753, "fa-circle": 61713, "fa-y-combinator-square": 61908, "fa-gbp": 61780, "fa-umbrella": 61673, "fa-sort-numeric-asc": 61794, "fa-pencil-square": 61771, "fa-soccer-ball-o": 61923, "fa-bicycle": 61958, "fa-user-times": 62005, "fa-trash-o": 61460, "fa-recycle": 61880, "fa-bell-slash-o": 61943, "fa-share-square-o": 61509, "fa-yc-square": 61908, "fa-users": 61632, "fa-yen": 61783, "fa-unlink": 61735, "fa-ban": 61534, "fa-sort-amount-asc": 61792, "fa-film": 61448, "fa-caret-down": 61655, "fa-file-text": 61788, "fa-list-alt": 61474, "fa-th-list": 61451, "fa-life-ring": 61901, "fa-filter": 61616, "fa-bluetooth-b": 62100, "fa-caret-up": 61656, "fa-sign-out": 61579, "fa-pencil": 61504, "fa-ticket": 61765, "fa-area-chart": 61950, "fa-skyatlas": 61974, "fa-opencart": 62013, "fa-hand-o-left": 61605, "fa-dedent": 61499, "fa-html5": 61755, "fa-at": 61946, "fa-dropbox": 61803, "fa-file-zip-o": 61894, "fa-long-arrow-up": 61814, "fa-stack-overflow": 61804, "fa-product-hunt": 62088, "fa-chevron-circle-left": 61751, "fa-check-square": 61770, "fa-cloud-download": 61677, "fa-caret-square-o-down": 61776, "fa-microphone-slash": 61745, "fa-folder": 61563, "fa-keyboard-o": 61724, "fa-bar-chart-o": 61568, "fa-transgender-alt": 61989, "fa-reddit-alien": 62081, "fa-openid": 61851, "fa-sort-asc": 61662, "fa-pause-circle": 62091, "fa-archive": 61831, "fa-eraser": 61741, "fa-cart-plus": 61975, "fa-inbox": 61468, "fa-truck": 61649, "fa-file-word-o": 61890, "fa-pied-piper-alt": 61864, "fa-object-ungroup": 62024, "fa-phone-square": 61592, "fa-eye": 61550, "fa-sun-o": 61829, "fa-folder-o": 61716, "fa-rebel": 61904, "fa-bars": 61641, "fa-cc-paypal": 61940, "fa-simplybuilt": 61973, "fa-won": 61785, "fa-hand-lizard-o": 62040, "fa-balance-scale": 62030, "fa-frown-o": 61721, "fa-repeat": 61470, "fa-arrow-circle-o-up": 61467, "fa-taxi": 61882, "fa-map-o": 62072, "fa-question": 61736, "fa-meh-o": 61722, "fa-terminal": 61728, "fa-caret-left": 61657, "fa-stop": 61517, "fa-tv": 62060, "fa-spoon": 61873, "fa-th": 61450, "fa-files-o": 61637, "fa-pause": 61516, "fa-mail-reply": 61714, "fa-cutlery": 61685, "fa-linux": 61820, "fa-battery-empty": 62020, "fa-hourglass-start": 62033, "fa-pause-circle-o": 62092, "fa-arrow-circle-left": 61608, "fa-shopping-cart": 61562, "fa-map-pin": 62070, "fa-youtube-play": 61802, "fa-drupal": 61865, "fa-bed": 62006, "fa-battery-quarter": 62019, "fa-weixin": 61911, "fa-exchange": 61676, "fa-gg-circle": 62049, "fa-angle-double-up": 61698, "fa-forward": 61518, "fa-hand-pointer-o": 62042, "fa-cog": 61459, "fa-reddit-square": 61858, "fa-arrow-circle-down": 61611, "fa-battery-three-quarters": 62017, "fa-venus-double": 61990, "fa-tumblr-square": 61812, "fa-angellist": 61961, "fa-toggle-off": 61956, "fa-info": 61737, "fa-eyedropper": 61947, "fa-behance": 61876, "fa-map-signs": 62071, "fa-file-audio-o": 61895, "fa-gavel": 61667, "fa-glass": 61440, "fa-hand-o-right": 61604, "fa-cube": 61874, "fa-pinterest-square": 61651, "fa-cc-stripe": 61941, "fa-battery-full": 62016, "fa-lightbulb-o": 61675, "fa-caret-square-o-up": 61777, "fa-video-camera": 61501, "fa-jsfiddle": 61900, "fa-long-arrow-left": 61815, "fa-caret-right": 61658, "fa-volume-down": 61479, "fa-tags": 61484, "fa-arrow-up": 61538, "fa-folder-open-o": 61717, "fa-shield": 61746, "fa-dashcube": 61968, "fa-rotate-right": 61470, "fa-angle-double-down": 61699, "fa-facebook": 61594, "fa-hand-scissors-o": 62039, "fa-scribd": 62090, "fa-sort": 61660, "fa-twitter-square": 61569, "fa-power-off": 61457, "fa-gratipay": 61828, "fa-mars": 61986, "fa-header": 61916, "fa-user": 61447, "fa-exclamation-circle": 61546, "fa-level-down": 61769, "fa-vine": 61898, "fa-tint": 61507, "fa-wordpress": 61850, "fa-expeditedssl": 62014, "fa-slack": 61848, "fa-cut": 61636, "fa-key": 61572, "fa-tripadvisor": 62050, "fa-opera": 62058, "fa-object-group": 62023, "fa-heartbeat": 61982, "fa-laptop": 61705, "fa-bomb": 61922, "fa-angle-double-left": 61696, "fa-quote-left": 61709, "fa-paw": 61872, "fa-reddit": 61857, "fa-cc-visa": 61936, "fa-circle-o": 61708, "fa-odnoklassniki-square": 62052, "fa-cc-jcb": 62027, "fa-mail-reply-all": 61730, "fa-lastfm": 61954, "fa-group": 61632, "fa-microphone": 61744, "fa-camera-retro": 61571, "fa-maxcdn": 61750, "fa-buysellads": 61965, "fa-play-circle-o": 61469, "fa-ge": 61905, "fa-i-cursor": 62022, "fa-percent": 62101, "fa-photo": 61502, "fa-toggle-on": 61957, "fa-fax": 61868, "fa-code-fork": 61734, "fa-y-combinator": 62011, "fa-map": 62073, "fa-try": 61845, "fa-diamond": 61977, "fa-neuter": 61996, "fa-quote-right": 61710, "fa-mobile": 61707, "fa-bell-slash": 61942, "fa-trademark": 62044, "fa-file-video-o": 61896, "fa-mixcloud": 62089, "fa-plus-circle": 61525, "fa-folder-open": 61564, "fa-css3": 61756, "fa-fast-forward": 61520, "fa-toggle-down": 61776, "fa-credit-card": 61597, "fa-caret-square-o-left": 61841, "fa-hourglass-3": 62035, "fa-hourglass-2": 62034, "fa-hourglass-1": 62033, "fa-angle-down": 61703, "fa-edge": 62082, "fa-trello": 61825, "fa-train": 62008, "fa-sheqel": 61963, "fa-file-powerpoint-o": 61892, "fa-arrow-left": 61536, "fa-television": 62060, "fa-life-saver": 61901, "fa-copy": 61637, "fa-sticky-note-o": 62026, "fa-mars-double": 61991, "fa-star-half-o": 61731, "fa-black-tie": 62078, "fa-chevron-up": 61559, "fa-chevron-down": 61560, "fa-fonticons": 62080, "fa-check-circle-o": 61533, "fa-plug": 61926, "fa-deviantart": 61885, "fa-dashboard": 61668, "fa-hourglass-o": 62032, "fa-plus": 61543, "fa-cc-discover": 61938, "fa-hashtag": 62098, "fa-gamepad": 61723, "fa-rub": 61784, "fa-history": 61914, "fa-sign-in": 61584, "fa-sort-amount-desc": 61793, "fa-rss-square": 61763, "fa-transgender": 61988, "fa-graduation-cap": 61853, "fa-whatsapp": 62002, "fa-mercury": 61987, "fa-amazon": 62064, "fa-medkit": 61690, "fa-bug": 61832, "fa-twitch": 61928, "fa-file-archive-o": 61894, "fa-forumbee": 61969, "fa-cny": 61783, "fa-arrows": 61511, "fa-map-marker": 61505, "fa-wheelchair": 61843, "fa-plus-square": 61694, "fa-male": 61827, "fa-institution": 61852, "fa-envelope-o": 61443, "fa-xing-square": 61801, "fa-step-forward": 61521, "fa-stumbleupon-circle": 61859, "fa-pencil-square-o": 61508, "fa-weibo": 61834, "fa-gear": 61459, "fa-rocket": 61749, "fa-bluetooth": 62099, "fa-search-plus": 61454, "fa-stop-circle": 62093, "fa-bell": 61683, "fa-undo": 61666, "fa-fast-backward": 61513, "fa-sliders": 61918, "fa-hotel": 62006, "fa-steam": 61878, "fa-hand-paper-o": 62038, "fa-circle-thin": 61915, "fa-share-square": 61773, "fa-asterisk": 61545, "fa-arrow-down": 61539, "fa-random": 61556, "fa-share-alt": 61920, "fa-beer": 61692, "fa-exclamation-triangle": 61553, "fa-commenting": 62074, "fa-volume-up": 61480, "fa-flag-checkered": 61726, "fa-ellipsis-h": 61761, "fa-hand-spock-o": 62041, "fa-crop": 61733, "fa-paragraph": 61917, "fa-battery-3": 62017, "fa-ellipsis-v": 61762, "fa-gift": 61547, "fa-strikethrough": 61644, "fa-motorcycle": 61980, "fa-life-bouy": 61901, "fa-reply-all": 61730, "fa-paper-plane-o": 61913, "fa-star-half": 61577, "fa-download": 61465, "fa-usb": 62087, "fa-chevron-circle-down": 61754, "fa-calculator": 61932, "fa-gg": 62048, "fa-contao": 62061, "fa-hand-o-down": 61607, "fa-leanpub": 61970, "fa-star-o": 61446, "fa-pie-chart": 61952, "fa-venus": 61985, "fa-inr": 61782, "fa-rupee": 61782, "fa-eur": 61779, "fa-tumblr": 61811, "fa-indent": 61500, "fa-mars-stroke-v": 61994, "fa-git": 61907, "fa-envelope": 61664, "fa-bitbucket-square": 61810, "fa-legal": 61667, "fa-gittip": 61828, "fa-chevron-left": 61523, "fa-cogs": 61573, "fa-arrow-circle-o-left": 61840, "fa-briefcase": 61617, "fa-user-md": 61680, "fa-angle-left": 61700, "fa-yc": 62011, "fa-long-arrow-right": 61816, "fa-coffee": 61684, "fa-copyright": 61945, "fa-toggle-up": 61777, "fa-support": 61901, "fa-youtube-square": 61798, "fa-cc-mastercard": 61937, "fa-unsorted": 61660, "fa-compress": 61542, "fa-android": 61819, "fa-font": 61489, "fa-arrow-right": 61537, "fa-minus": 61544, "fa-bitbucket": 61809, "fa-facebook-f": 61594, "fa-subway": 62009, "fa-headphones": 61477, "fa-paperclip": 61638, "fa-industry": 62069, "fa-rmb": 61783, "fa-minus-square": 61766, "fa-moon-o": 61830, "fa-file-excel-o": 61891, "fa-line-chart": 61953, "fa-fighter-jet": 61691, "fa-sort-alpha-desc": 61790, "fa-spotify": 61884, "fa-star-half-empty": 61731, "fa-share": 61540, "fa-comment": 61557, "fa-mars-stroke": 61993, "fa-stack-exchange": 61837, "fa-pied-piper": 61863, "fa-building": 61869, "fa-thumbs-up": 61796, "fa-chevron-circle-right": 61752, "fa-adjust": 61506, "fa-sellsy": 61971, "fa-paypal": 61933, "fa-signal": 61458, "fa-sort-up": 61662, "fa-shekel": 61963, "fa-codiepie": 62084, "fa-calendar-plus-o": 62065, "fa-digg": 61862, "fa-save": 61639, "fa-shopping-bag": 62096, "fa-eye-slash": 61552, "fa-backward": 61514, "fa-hand-stop-o": 62038, "fa-mail-forward": 61540, "fa-link": 61633, "fa-table": 61646, "fa-tag": 61483, "fa-turkish-lira": 61845, "fa-envelope-square": 61849, "fa-optin-monster": 62012, "fa-money": 61654, "fa-instagram": 61805, "fa-volume-off": 61478, "fa-unlock-alt": 61758, "fa-minus-circle": 61526, "fa-hacker-news": 61908, "fa-hand-grab-o": 62037, "fa-adn": 61808, "fa-list-ol": 61643, "fa-magnet": 61558, "fa-calendar-minus-o": 62066, "fa-linkedin": 61665, "fa-paper-plane": 61912, "fa-mouse-pointer": 62021, "fa-reply": 61714, "fa-smile-o": 61720, "fa-hourglass-half": 62034, "fa-behance-square": 61877, "fa-twitter": 61593, "fa-expand": 61541, "fa-flask": 61635, "fa-flash": 61671, "fa-trophy": 61585, "fa-long-arrow-down": 61813, "fa-odnoklassniki": 62051, "fa-angle-double-right": 61697, "fa-home": 61461, "fa-bolt": 61671, "fa-italic": 61491, "fa-comments": 61574, "fa-commenting-o": 62075, "fa-toggle-right": 61778, "fa-file": 61787, "fa-bold": 61490, "fa-internet-explorer": 62059, "fa-cc-amex": 61939, "fa-sort-down": 61661, "fa-anchor": 61757, "fa-medium": 62010, "fa-calendar": 61555, "fa-superscript": 61739, "fa-wechat": 61911, "fa-file-text-o": 61686, "fa-cloud": 61634, "fa-user-plus": 62004, "fa-times": 61453, "fa-street-view": 61981, "fa-trash": 61944, "fa-paste": 61674, "fa-ambulance": 61689, "fa-suitcase": 61682, "fa-binoculars": 61925, "fa-user-secret": 61979, "fa-sort-alpha-asc": 61789, "fa-picture-o": 61502, "fa-cc": 61962, "fa-calendar-times-o": 62067, "fa-phone": 61589, "fa-github-square": 61586, "fa-hand-peace-o": 62043, "fa-windows": 61818, "fa-500px": 62062, "fa-calendar-check-o": 62068, "fa-clock-o": 61463, "fa-connectdevelop": 61966, "fa-text-height": 61492, "fa-houzz": 62076, "fa-align-right": 61496, "fa-angle-right": 61701, "fa-hand-rock-o": 62037, "fa-heart-o": 61578, "fa-steam-square": 61879, "fa-underline": 61645, "fa-file-image-o": 61893, "fa-bus": 61959, "fa-play-circle": 61764, "fa-plus-square-o": 61846, "fa-rss": 61598, "fa-battery-0": 62020, "fa-battery-1": 62019, "fa-battery-2": 62018, "fa-google-plus-square": 61652, "fa-battery-4": 62016, "fa-caret-square-o-right": 61778, "fa-child": 61870, "fa-space-shuttle": 61847, "fa-pinterest-p": 62001, "fa-outdent": 61499, "fa-lock": 61475, "fa-dot-circle-o": 61842, "fa-git-square": 61906, "fa-clipboard": 61674, "fa-mortar-board": 61853, "fa-university": 61852, "fa-github": 61595, "fa-jpy": 61783, "fa-vk": 61833, "fa-print": 61487, "fa-code": 61729, "fa-book": 61485, "fa-pinterest": 61650, "fa-youtube": 61799, "fa-fire": 61549, "fa-hourglass-end": 62035, "fa-tasks": 61614, "fa-xing": 61800, "fa-ioxhost": 61960, "fa-play": 61515, "fa-flag-o": 61725, "fa-battery-half": 62018, "fa-search": 61442, "fa-genderless": 61997, "fa-renren": 61835, "fa-database": 61888, "fa-plane": 61554, "fa-sort-numeric-desc": 61795, "fa-intersex": 61988, "fa-tree": 61883, "fa-scissors": 61636, "fa-question-circle": 61529, "fa-close": 61453, "fa-crosshairs": 61531, "fa-apple": 61817, "fa-wrench": 61613, "fa-sitemap": 61672, "fa-language": 61867, "fa-automobile": 61881, "fa-hourglass": 62036, "fa-bar-chart": 61568, "fa-file-o": 61462, "fa-krw": 61785, "fa-soundcloud": 61886, "fa-floppy-o": 61639, "fa-upload": 61587, "fa-arrow-circle-o-right": 61838, "fa-info-circle": 61530, "fa-cloud-upload": 61678, "fa-facebook-official": 62000, "fa-search-minus": 61456, "fa-music": 61441, "fa-stumbleupon": 61860, "fa-star-half-full": 61731, "fa-file-picture-o": 61893, "fa-image": 61502, "fa-mobile-phone": 61707, "fa-dollar": 61781, "fa-google-wallet": 61934, "fa-feed": 61598, "fa-vimeo": 62077, "fa-futbol-o": 61923, "fa-hdd-o": 61600, "fa-remove": 61453, "fa-bullseye": 61760, "fa-location-arrow": 61732, "fa-female": 61826, "fa-joomla": 61866, "fa-thumb-tack": 61581, "fa-align-justify": 61497, "fa-external-link": 61582, "fa-arrow-circle-right": 61609, "fa-level-up": 61768, "fa-gears": 61573, "fa-foursquare": 61824, "fa-venus-mars": 61992, "fa-yelp": 61929, "fa-exclamation": 61738, "fa-star": 61445, "fa-google-plus": 61653, "fa-ra": 61904, "fa-h-square": 61693, "fa-lastfm-square": 61955, "fa-registered": 62045, "fa-edit": 61508, "fa-unlock": 61596, "fa-sort-desc": 61661, "fa-tencent-weibo": 61909, "fa-thumbs-down": 61797, "fa-eject": 61522, "fa-linkedin-square": 61580, "fa-pagelines": 61836, "fa-chain": 61633, "fa-yahoo": 61854, "fa-send": 61912, "fa-check": 61452, "fa-compass": 61774, "fa-viacoin": 62007, "fa-angle-up": 61702, "fa-wikipedia-w": 62054, "fa-qrcode": 61481, "fa-paint-brush": 61948, "fa-bookmark-o": 61591, "fa-usd": 61781, "fa-chevron-right": 61524, "fa-ruble": 61784, "fa-delicious": 61861, "fa-btc": 61786, "fa-lemon-o": 61588, "fa-arrow-circle-up": 61610, "fa-comments-o": 61670, "fa-chrome": 62056, "fa-check-square-o": 61510, "fa-ils": 61963, "fa-birthday-cake": 61949, "fa-tablet": 61706, "fa-codepen": 61899, "fa-stop-circle-o": 62094, "fa-chain-broken": 61735, "fa-puzzle-piece": 61742, "fa-creative-commons": 62046, "fa-spinner": 61712, "fa-newspaper-o": 61930, "fa-globe": 61612, "fa-firefox": 62057, "fa-vimeo-square": 61844, "fa-magic": 61648, "fa-align-center": 61495, "fa-warning": 61553, "fa-desktop": 61704, "fa-cc-diners-club": 62028, "fa-thumbs-o-up": 61575, "fa-dribbble": 61821, "fa-square-o": 61590, "fa-columns": 61659, "fa-flickr": 61806, "fa-retweet": 61561, "fa-flag": 61476, "fa-google": 61856, "fa-file-sound-o": 61895, "fa-bitcoin": 61786, "fa-text-width": 61493, "fa-arrows-v": 61565, "fa-hospital-o": 61688, "fa-step-backward": 61512, "fa-bullhorn": 61601, "fa-fire-extinguisher": 61748, "fa-arrows-h": 61566, "fa-refresh": 61473, "fa-fort-awesome": 62086, "fa-github-alt": 61715, "fa-reorder": 61641, "fa-modx": 62085, "fa-facebook-square": 61570, "fa-empire": 61905, "fa-credit-card-alt": 62083, "fa-qq": 61910, "fa-rouble": 61784, "fa-minus-square-o": 61767, "fa-rotate-left": 61666, "fa-file-pdf-o": 61889}
================================================
FILE: kivystudio/tools/iconfonts/test/main.py
================================================
from kivy.uix.label import Label
from kivy.uix.boxlayout import BoxLayout
from kivy.base import runTouchApp
from kivy.lang import Builder
import os, json ,sys
from os.path import join, dirname
p =(dirname(dirname(join(os.getcwd(), __file__))))
print(p)
sys.path.append(p)
from iconfonts import register, icon
font_file = join(dirname(__file__), 'font-awesome.fontd')
register('awesome_font', 'font-awesome.ttf',
font_file)
with open(font_file, 'r') as f:
fontd = json.loads(f.read())
class Boxer(BoxLayout):
def search(self, text):
add_icons(text)
root = Builder.load_string('''
Boxer:
orientation: 'vertical'
TextInput:
size_hint_y: None
height: '48dp'
on_text: root.search(self.text)
ScrollView:
bar_width: '24dp'
GridLayout:
id: grid
height: self.minimum_height
size_hint_y: None
cols: 1
''')
def add_icons(search=''):
root.ids.grid.clear_widgets()
keys = list(fontd.keys())
keys.sort()
for icon_name in keys:
if search and icon_name.find(search)==-1:
continue
lb = Label(markup=True, size_hint_y=None)
lb.text= '[color=3280ff]%s[/color] '%(icon(icon_name, 32)) + icon_name
root.ids.grid.add_widget(lb)
add_icons()
runTouchApp(root)
================================================
FILE: kivystudio/tools/infolabel.py
================================================
from kivy.uix.label import Label
from kivy.lang import Builder
from kivy.core.window import Window
from .__init__ import set_auto_mouse_position
def show_info_on_mouse(message=''):
''' func that displays an info
on mouse cursor'''
if message:
if info_label in Window.children:
Window.remove_widget(info_label)
info_label.text = ''
info_label.text = message
set_auto_mouse_position(info_label)
Window.add_widget(info_label)
def remove_info_on_mouse():
if info_label in Window.children:
Window.remove_widget(info_label)
class InfoLabel(Label):
pass
Builder.load_string('''
:
size_hint: None,None
text_size: None, self.height
valign: 'middle'
halign:'left'
width: self.texture_size[0]+10
height: '30dp'
color: 1,1,1,1
padding: '4dp', '4dp'
canvas.before:
Color:
rgba: 0,0,0,.9
RoundedRectangle:
size: self.size
pos: self.pos
''')
info_label = InfoLabel()
================================================
FILE: kivystudio/tools/logger.py
================================================
from kivy.logger import Logger as KivyLogger
from kivy.utils import get_hex_from_color, escape_markup
COLORS = {
'info': (.3,1,.4,1),
'warning': (1,1,0,1),
'error': (1,.5,.2,1),
}
class LoggerBase:
def _format_log(self, log_type, msg):
msg = msg.split(':',1) + ['']
log_color = get_hex_from_color(COLORS[log_type])
log_title = "[color=%s]&bl;[b]%-14s[/b]&br;[/color] " \
% (log_color, log_type.upper())
if msg[1]:
log_msg = "[%-18s] %s" % (msg[0], msg[1])
else:
log_msg = "%s" % msg[0]
return log_title+log_msg
def info(self, msg, log_out=False):
log = self._format_log('info', msg)
self._log_out(msg, log, 'info', log_out=log_out)
def warning(self, msg, log_out=False):
log = self._format_log('warning', msg)
self._log_out(msg, log, 'warning', log_out=log_out)
def error(self, msg, log_out=False):
log = self._format_log('error', msg)
self._log_out(msg, log, 'error', log_out=log_out)
def _log_out(self, msg, log, log_type, log_out=False):
from kivystudio.components.codeplace import terminal
terminal.logger.log(log)
if log_out:
getattr(KivyLogger, log_type)(msg)
def clear_logs(self):
from kivystudio.components.codeplace import terminal
terminal.logger.clear_logs()
Logger = LoggerBase()
from kivy.clock import mainthread
@mainthread
def test_log(*args):
Logger.info('Welcome to KivyStudio')
test_log()
================================================
FILE: kivystudio/tools/quicktools.py
================================================
from kivy.logger import Logger
import sys
from kivystudio.widgets.filemanager import filemanager
def open_new_file():
''' open a new file on
the CodeInput '''
from kivystudio.assembler import code_place
code_place.add_code_tab(tab_type='new_file')
def open_file(filename=''):
''' open a file (existing file) on
the CodeInput, if filename, it open a
new tab for the file, else it opens a filechooser'''
from kivystudio.assembler import add_new_tab
if filename:
add_new_tab([filename,])
else:
filemanager.open_file(path='/root',on_selection=add_new_tab)
def open_folder(folder=''):
''' open a folder, if folder, it open a
new project, else it opens a filechooser'''
from kivystudio.assembler import open_project
if folder:
open_project([folder,])
else:
filemanager.choose_dir(path='.',on_selection=open_project)
def open_recent():
pass
def save():
''' save the current opened
file '''
from kivystudio.assembler import code_place
code_place.code_manager.save_current_tab()
def save_all():
''' save all file currently opened '''
from kivystudio.assembler import code_place
code_place.code_manager.save_all_tabs()
def save_as():
pass
def exit_window():
exit()
import string
def is_binary(filename):
''' checks if a file is a binary file or not
used to validate file before opening them in
the studio '''
s = open(filename, 'rb').read(512)
def func(num):
return bytes(chr(num).encode('utf-8'))
text_char = b''.join( list(map(func, range(32,127))) ) #+ list('\n\r\t\b') )
if sys.version_info[0] == 3:
_null_trans = b''.maketrans(b"",b"")
else:
_null_trans = string.maketrans('', '')
if not s: # empty files are considered text files
return False
if b'\0' in s:
# file with null bytes are likely binary
return True
t = s.translate(_null_trans, delete=text_char)
# t = s.translate(str.maketrans('', '', text_char))
# if more than 30% are non-text charaters
# then it is considered binary
if float(len(t))/float(len(s)) > 0.30:
return True
return False
================================================
FILE: kivystudio/widgets/__init__.py
================================================
================================================
FILE: kivystudio/widgets/codeinput/__init__.py
================================================
from kivy.core.window import Window
from kivy.uix.gridlayout import GridLayout
from kivy.uix.label import Label
from kivy.uix.behaviors import ToggleButtonBehavior
from kivy.uix.behaviors import FocusBehavior
from kivy.uix.scrollview import ScrollView
from kivy.clock import Clock
from kivy.metrics import dp
from kivy.lang import Builder
from kivy.properties import BooleanProperty, StringProperty, ListProperty, ObjectProperty
from kivy.utils import get_color_from_hex
from kivystudio.behaviors import HoverBehavior
from kivystudio.widgets.rightclick_drop import RightClickDrop
from .styles import NativeTweakStyle
from .codeinput import CodeInput
from .code_find import CodeInputFind
from .code_extra_behavior import CodeExtraBehavior
import os
from pygments import styles
class CodeInputDropDown(RightClickDrop):
''' DropDown widget show when mouse
is right clicked on CodeInput
'''
def on_touch_down(self, touch):
if self.collide_point(*touch.pos): # so doesn't unfocus input
FocusBehavior.ignored_touch.append(touch)
return super(CodeInputDropDown,self).on_touch_down(touch)
def open(self, code_input):
self.codeinput = codeinput
super(CodeInputDropDown,self).open()
def copy(self):
self.codeinput.copy()
def paste(self):
self.codeinput.paste()
def cut(self):
self.codeinput.cut()
class InnerCodeInput(HoverBehavior, CodeExtraBehavior, CodeInput):
path = StringProperty('')
'''Path of the current file
`path` is a :class:`~kivy.properties.StringProperty`
and defaults to ''
'''
rightclick_dropdown = None
''' drop down menu that appears when the right click button is clicked
`rightclick_dropdown` is an instance of .code_find.CodeInputFind
'''
code_finder = None
''' '''
def __init__(self, **kwargs):
super(InnerCodeInput, self).__init__(**kwargs)
self.style_name = 'native'
self.background_normal= ''
self.background_active= ''
def on_style_name(self, *args):
self.style = NativeTweakStyle
self.background_color = get_color_from_hex(self.style.background_color)
self._trigger_refresh_text()
def on_text(self, *args):
if self.focus:
self.parent.saved = False
self.check_settings()
def check_settings(self):
from kivystudio.settings import settings_obj
auto_save = settings_obj.auto_save
auto_emulate = settings_obj.auto_emulate
if auto_save:
self.parent.parent.save_file(auto_save=True)
if auto_emulate:
from kivystudio.parser import emulate_file
from kivystudio.components.emulator_area import get_emulator_area
if get_emulator_area().emulation_file == self.parent.filename:
emulate_file(self.parent.filename)
def keyboard_on_textinput(self, window, text):
'overiding the default textinput keyboard listener '
if (text == '=' or text == '-') and 'ctrl' in Window.modifiers:
return True
super(InnerCodeInput,self).keyboard_on_textinput(window, text)
def keyboard_on_key_down(self, keyboard, keycode, text, modifiers):
'overiding the default keyboard listener '
# print(keycode, modifiers)
if keycode[1] == 'tab' and 'shift' in modifiers: # unindentation [Shit-tab]
self._do_reverse_indentation()
elif keycode[1] == 'tab' and self.selection_text: # multiple indentation [Tab]
self.do_multiline_indent()
elif keycode[1] == 'backspace' and 'ctrl' in modifiers: # delete word left [Ctrl-bsc]
self.delete_word_left()
elif keycode[1] == '/' and 'ctrl' in modifiers: # a comment ctrl /
self.do_comment()
elif keycode[1] == 'enter':
Clock.schedule_once(lambda dt: self.do_auto_indent())
return super(CodeInput, self).keyboard_on_key_down(keyboard, keycode, text, modifiers)
elif keycode[1] == 'f' and 'ctrl' in modifiers: # ctrl f to open a search
self.open_code_finder()
elif keycode[0] == 27: # on escape, do nothing
return True
else:
# then return super for others
return super(CodeInput, self).keyboard_on_key_down(keyboard, keycode, text, modifiers)
def open_code_finder(self):
''' open the search finder
on the codeinput '''
code_finder = InnerCodeInput.code_finder
if code_finder:
code_finder.open(self)
else:
InnerCodeInput.code_finder = CodeInputFind()
InnerCodeInput.code_finder.open(self)
def open_rightclick_dropdown(self):
''' open the dropdown right click
on the codeinput '''
rightclick_dropdown = InnerCodeInput.rightclick_dropdown
if rightclick_dropdown:
rightclick_dropdown.open()
else:
InnerCodeInput.rightclick_dropdown = CodeInputDropDown()
InnerCodeInput.rightclick_dropdown.open(self)
def on_hover(self, *a):
''' changing the mouse cursor
on code input'''
if self.hover:
Window.set_system_cursor('ibeam') # set cursor to ibeam
else:
Window.set_system_cursor('arrow') # set cursor to arrow
def on_touch_down(self, touch):
if self.collide_point(*touch.pos):
if touch.button == 'right':
self.open_rightclick_dropdown()
FocusBehavior.ignored_touch.append(touch)
return True
if touch.button == 'left':
return super(InnerCodeInput,self).on_touch_down(touch)
class NumberingGrid(ScrollView):
def on_touch_down(self, touch):
if self.collide_point( *touch.pos):
FocusBehavior.ignored_touch.append(touch)
super(NumberingGrid, self).on_touch_down(touch)
class ScrollingBar(ScrollView):
def on_touch_down(self, touch):
if self.collide_point( *touch.pos):
FocusBehavior.ignored_touch.append(touch)
super(ScrollingBar, self).on_touch_down(touch)
class FullCodeInput(GridLayout):
_do_cursor_scroll =BooleanProperty(True)
code_input = ObjectProperty(None)
tab = ObjectProperty(None)
tab_type = StringProperty('code')
filename = StringProperty('')
''' name current file in the input
:data:`filename` is a :class:`~kivy.properties.StringProperty`
and defaults to ''
'''
saved = BooleanProperty(True)
'''Indicates if the current file is saved or not
:data:`saved` is a :class:`~kivy.properties.BooleanProperty`
and defaults to True
'''
def __init__(self, **kwargs):
super(FullCodeInput, self).__init__(**kwargs)
Clock.schedule_once(self.first_number)
self._former_line_lenght = 1
self.first_time = True
def first_number(self, dt):
label = Numbers_(height=dp(self.ids.code_input.line_height), text=str(1))
Clock.schedule_once(lambda dt: setattr(label, 'state', 'down'))
label.fbind('on_press', self._number_pressed)
self.ids.numbering.add_widget(label)
self._former_line_lenght = 1
def _number_pressed(self, lb):
self.ids.code_input.cursor = (self.ids.code_input.cursor[0], int(lb.text)-1)
def change_scroll_y(self, txt, scroll):
if self._do_cursor_scroll:
lines_lenght = len(txt._lines)
line_pos = txt.cursor_row +1
norm_y = float(line_pos) / lines_lenght
scroll.scroll_y = abs(norm_y-1)
if line_pos == 1:
scroll.scroll_y = 1
# scroll scroll numbers
line_num = txt.cursor_row + 1
children = self.ids.numbering.children[::-1]
if children:
child = children[line_num-1]
self.ids.number_scroll.scroll_to(child, dp(5))
Clock.schedule_once(lambda dt: setattr(child, 'state', 'down'))
def toggle(chd):
if chd!=child:
chd.state='normal'
map(lambda child: toggle, ToggleButtonBehavior.get_widgets(child.group))
def do_bar_scroll(self, txt, scroll):
lines_lenght = len(txt._lines)
line_pos = int(abs(scroll.scroll_y-1) * lines_lenght)
cursor_y = abs(line_pos)
txt.cursor = (txt.cursor_col, cursor_y)
def number_me(self, txt, scroll):
lines_lenght = len(txt._lines)
line_pos = txt.cursor_row +2
if not(lines_lenght <= 1) or not(self.first_time):
if lines_lenght >= self._former_line_lenght:
if line_pos == lines_lenght:
self.do_new_line(txt, scroll)
else:
self.do_new_line(txt, scroll)
else:
self.remove_line_numbering(txt, scroll)
self._former_line_lenght = lines_lenght
self.first_time = False
def do_new_line(self, txt, scroll):
lines_lenght = len(txt._lines)
for line_num in range(self._former_line_lenght+1, lines_lenght+1):
label = Numbers_(height=dp(self.ids.code_input.line_height), text=str(line_num))
label.fbind('on_press', self._number_pressed)
self.ids.numbering.add_widget(label)
def remove_line_numbering(self, txt, scroll):
lines_lenght = len(txt._lines)
for line_num in range(lines_lenght+1, self._former_line_lenght+1):
child = self.ids.numbering.children[0]
self.ids.numbering.remove_widget(child)
class Numbers_(ToggleButtonBehavior, Label):
selected_color = ListProperty([1,1,1,1])
' color of the number when selected'
normal_color = ListProperty([.5,.5,.5,1])
' color of the number when selected'
def on_state(self, *args):
if self.state == 'down':
self.color = self.selected_color
self.canvas_color = self.normal_color[:3]+[.3]
else:
self.color = self.normal_color
self.canvas_color = [0,0,0,0]
Builder.load_file(os.path.join(os.path.dirname(__file__),'codeinput.kv'))
if __name__ == "__main__":
from kivy.base import runTouchApp
runTouchApp(FullCodeInput())
'rrt, xcode, friendly, algol'
================================================
FILE: kivystudio/widgets/codeinput/code_extra_behavior.py
================================================
''' Etra Awesome CodeInput Mixin/Behavior
'''
class CodeExtraBehavior(object):
def do_comment(self):
' comment a line'
if not self.selection_text: # single line momment
line = self._lines[self.cursor_row]
self.do_one_line_comment(line)
else: # multiline comment
i = self._selection_from
j = self._selection_to
check_lines = list(filter(lambda line: line != '', self.selection_text.splitlines()))
lines = self.selection_text.splitlines()
if j > i:
# basically checking for lines that has a value
# move the cursor position
self.cursor = (0, self.cursor_row- len(lines))
# check if there are no comments in the lines
if len(list(filter(lambda line: line.lstrip().startswith('#'), check_lines))) != len(check_lines):
self.do_multiline_comment(lines)
else:
self.uncomment_multiline(lines)
else:
self.cursor = (0, self.cursor_row)
# check if there are no comments in the lines
if len(list(filter(lambda line: line.lstrip().startswith('#'), check_lines))) != len(check_lines):
self.do_multiline_comment(lines)
else:
self.uncomment_multiline(lines)
def do_multiline_comment(self, lines):
'comment multiple lines'
closest = self.get_closest_indentation(lines)
y_cur = self.cursor[1]
for i, line in enumerate(lines):
if line:
former_cur_x = self.cursor[0]
self.cursor = (closest, y_cur+i)
# if line already commented
if line.lstrip().startswith('#'): continue
self.insert_text('# ')
self.cursor = (former_cur_x, self.cursor[1])
# now cancel selection
self.cancel_selection()
def do_multiline_indent(self):
lines = self.selection_text.splitlines()
closest = self.get_closest_indentation(lines)
y_cur = self.cursor[1]
for i, line in enumerate(lines):
if line:
former_cur_x = self.cursor[0]
self.cursor = (closest, y_cur-i-1)
self.insert_text(' '*4)
self.cursor = (former_cur_x, self.cursor[1])
# now cancel selection
self.cancel_selection()
def uncomment_multiline(self, lines):
'''uncomment multiple line when the user from
the user selection'''
y_cur = self.cursor[1]
for i, line in enumerate(lines):
if line:
former_cur_x = self.cursor[0]
find = line.find('#')
self.cursor = (find, y_cur+i)
if line.lstrip().startswith('# '):
offset = 2
substring = '# '
elif line.lstrip().startswith('#'):
offset = 1
substring = '#'
new_line = line[:find] + line[find+offset:]
self._set_line_text(self.cursor_row, new_line)
self._set_my_undo_redo(find, offset, substring)
self._do_my_refresh(new_line)
self.cursor = (former_cur_x, self.cursor[1])
# now cancel selection
self.cancel_selection()
def do_one_line_comment(self, line):
if line:
if not line.lstrip().startswith('#'): # then comment
strip_line = line.strip()
former_cur_x = self.cursor[0]
self.cursor = (line.index(strip_line), self.cursor[1])
self.insert_text('# ')
self.cursor = (former_cur_x, self.cursor[1])
else: # uncomment line
if line.lstrip().startswith('# '):
offset = 2
substring = '# '
elif line.lstrip().startswith('#'):
offset = 1
substring = '#'
find = line.find('#')
new_line = line[:find] + line[find+offset:]
self._set_line_text(self.cursor_row, new_line)
self._set_my_undo_redo(find, offset, substring)
self._do_my_refresh(new_line)
def _do_my_refresh(self, new_text):
'the internal kivy code used to refreash the textinput'
# refresh just the current line instead of the whole text
start, finish, lines, lineflags, len_lines =\
self._get_line_from_cursor(self.cursor_row, new_text)
# avoid trigger refresh, leads to issue with
# keys/text send rapidly through code.
self._refresh_text_from_property('del', start, finish, lines,
lineflags, len_lines)
def _set_my_undo_redo(self, find, offset, substring):
if self.cursor_row > 0:
old_index = len('\n'.join(self._lines[:self.cursor_row])) + find - 1
new_index = old_index + offset
else:
old_index = find
new_index = old_index
# handle undo and redo
self._set_undo_redo_bkspc(
old_index,
new_index,
substring, False)
def do_auto_indent(self):
''' and i guess your are thinking doesn't kivy already has an auto indent,
this func just check if the line endswith a ':' so it would indent it to the
next line
'''
line = self._lines[self.cursor_row-1].strip() # get the previos line then strip it
if line.endswith(':'):
self.insert_text('\t')
def get_closest_indentation(self, lines):
counter = []
for line in lines:
count = 0
for char in line:
if char != ' ':
counter.append(count)
if count == 0: # if we found an indent at 0 then look no further
break
break
count += 1
else:
if line:
counter.append(len(line))
return min(counter)
def _do_reverse_indentation(self):
if not self.selection_text and self._lines[self.cursor_row]:
line = self._lines[self.cursor_row].replace('\t', ' ')
indent = self.get_closest_indentation([line])
if indent != 0:
next_indent = indent - 4
new_line = line[:next_indent] + line[indent:]
self._set_line_text(self.cursor_row, new_line)
self._set_my_undo_redo(next_indent-2, 4, ' ')
self._do_my_refresh(new_line)
self.cursor = (next_indent, self.cursor_row)
if self.selection_text:
i = self._selection_from
j = self._selection_to
lines = self.selection_text.splitlines()
if j > i:
self.cursor = (0, self.cursor_row- len(lines))
else:
self.cursor = (0, self.cursor_row)
self._multi_unindent(lines)
# now cancel selection for the main time
self.cancel_selection()
def _multi_unindent(self, lines):
y_cur = self.cursor[1]
for i, line in enumerate(lines):
if line:
former_cur_x = self.cursor[0]
self.cursor = (0, y_cur+i)
line = line.replace('\t', ' ')
indent = self.get_closest_indentation([line])
if indent != 0:
next_indent = indent - 4
new_line = line[:next_indent] + line[indent:]
self._set_line_text(self.cursor_row, new_line)
self._set_my_undo_redo(next_indent-2, 4, ' ')
self._do_my_refresh(new_line)
self.cursor = (next_indent, self.cursor_row)
def delete_word_left(self):
'''
Delete text left of the cursor to the beginning of word'''
if self._selection:
return None
line = self._lines[self.cursor[1]]
if self.cursor[0]==0:
return None
if line.strip()=='':
return None
former_cursor_x = self.cursor[0]
old_index = self.cursor_index()
self.do_cursor_movement('cursor_left', control=True)
new_index = self.cursor_index()
end_cursor = self.cursor
if self.cursor[0] != former_cursor_x:
new_line = line[:self.cursor[0]] + line[former_cursor_x:]
self._set_line_text(self.cursor_row, new_line)
substring = line[self.cursor[0]:former_cursor_x]
self._set_undo_redo_bkspc(
old_index,
new_index,
substring, False)
self._do_my_refresh(new_line)
self._set_cursor(pos=end_cursor)
def _split_smart(self, text):
''' turns out this function isn't really smart
so had to override it, basically for horizontal line continuation'''
lines = text.split(u'\n')
lines_flags = [0] + [0x01] * (len(lines) - 1)
return lines, lines_flags
================================================
FILE: kivystudio/widgets/codeinput/code_find.py
================================================
from kivy.properties import BooleanProperty, ObjectProperty, StringProperty
from kivy.uix.boxlayout import BoxLayout
from kivy.core.window import Window
from kivy.clock import Clock
from kivystudio.widgets.searchinput import SearchInput
class CodeInputFind(BoxLayout):
'''Widget responsible for searches in Code Input
'''
query = StringProperty('')
'''Search query
:data:`query` is a :class:`~kivy.properties.StringProperty`
'''
txt_query = ObjectProperty(None)
'''Search query TextInput
:data:`txt_query` is a :class:`~kivy.properties.ObjectProperty`
'''
use_regex = BooleanProperty(False)
'''Filter search with regex
:data:`use_regex` is a :class:`~kivy.properties.BooleanProperty`
'''
case_sensitive = BooleanProperty(False)
'''Filter search with case sensitive text
:data:`case_sensitive` is a :class:`~kivy.properties.BooleanProperty`
'''
code_input = ObjectProperty(None)
def on_touch_down(self, touch):
'''Enable touche
'''
if self.collide_point(*touch.pos):
super(CodeInputFind, self).on_touch_down(touch)
return True
def find_next(self, search):
'''Find the next occurrence of the string according to the cursor
position
'''
code_input = self.code_input
use_regex = self.use_regex
case = self.case_sensitive
text = code_input.text
if not case:
text = text.upper()
search = search.upper()
lines = text.splitlines()
col = code_input.cursor_col
row = code_input.cursor_row
found = -1
size = 0 # size of string before selection
line = None
search_size = len(search)
for i, line in enumerate(lines):
if i >= row:
if use_regex:
if i == row:
line_find = line[col + 1:]
else:
line_find = line[:]
found = re.search(search, line_find)
if found:
search_size = len(found.group(0))
found = found.start()
else:
found = -1
else:
# if on current line, consider col
if i == row:
found = line.find(search, col + 1)
else:
found = line.find(search)
# has found the string. found variable indicates the initial po
if found != -1:
code_input.cursor = (found, i)
break
size += len(line)
if found != -1:
pos = text.find(line) + found
code_input.select_text(pos, pos + search_size)
return True
else:
return False
def find_prev(self, search):
'''Find the previous occurrence of the string according to the cursor
position
'''
code_input = self.code_input
use_regex = self.use_regex
case = self.case_sensitive
code_input = self.code_input
text = code_input.text
if not case:
text = text.upper()
search = search.upper()
lines = text.splitlines()
col = code_input.cursor_col
row = code_input.cursor_row
lines = lines[:row + 1]
lines.reverse()
line_number = len(lines)
found = -1
line = None
search_size = len(search)
for i, line in enumerate(lines):
i = line_number - i - 1
if use_regex:
if i == row:
line_find = line[:col]
else:
line_find = line[:]
found = re.search(search, line_find)
if found:
search_size = len(found.group(0))
found = found.start()
else:
found = -1
else:
# if on current line, consider col
if i == row:
found = line[:col].find(search)
else:
found = line.find(search)
# has found the string. found variable indicates the initial po
if found != -1:
code_input.cursor = (found, i)
break
if found != -1:
pos = text.find(line) + found
code_input.select_text(pos, pos + search_size)
return True
else:
return False
def find(self,search):
if not self.find_next(search):
self.find_prev(search)
Clock.schedule_once(lambda *args: setattr(self.ids.input, 'focus', True))
def open(self,code_input):
self.code_input=code_input
self.ids.input.focus=True
if not self.parent:
Window.add_widget(self)
self.top = code_input.top
self.right= code_input.right
code_input.bind(top=self.setter('top'))
code_input.bind(right=self.setter('right'))
def dismiss(self):
if self.parent:
self.parent.remove_widget(self)
================================================
FILE: kivystudio/widgets/codeinput/codeinput.kv
================================================
:
cols: 3
code_input: code_input
NumberingGrid:
id: number_scroll
size_hint_x: None
width: '50dp'
bar_color: 1,0,0,0
bar_inactive_color: self.bar_color
scroll_type: ['bars']
bar_width: 0
canvas.before:
Color:
rgba: (0.12, 0.12, 0.125, 1)
Rectangle:
size: self.size
pos: self.pos
# canvas to show division
canvas.after:
Color:
rgba: (0, 0, 0, .4)
Line:
points: [self.right,self.y, self.right,self.height]
Color:
rgba: (0, 0, 0, .3)
Line:
points: [self.right+1,self.y, self.right+1,self.height]
Color:
rgba: (0, 0, 0, .2)
Line:
points: [self.right+2,self.y, self.right+2,self.height]
Color:
rgba: (0, 0, 0, .1)
Line:
points: [self.right+3,self.y, self.right+3,self.height]
width: 2
GridLayout:
cols: 1
id: numbering
size_hint_y: None
height: self.minimum_height
padding: [0, '6dp', 0, '6dp']
InnerCodeInput:
id: code_input
auto_indent: True
size_hint_y: 1
height: scroll.height
cursor_color: 1,1,1,1
on_cursor_row: root.change_scroll_y(code_input, scroll)
_line_lenght: len(self._lines)
on__line_lenght: root.number_me(self, scroll)
selection_color: .8,.8,.8,.4
font_size: sp(14) * settings.dpi_scale
line_highlight_color: 1,1,.8,.1
on_selection_text:
if self.selection_text: self.line_highlight_color = 0,0,0,0
else: self.line_highlight_color = 1,1,.8,.1
on_focus:
if not self.focus: self.line_highlight_color = 0,0,0,0
else: self.line_highlight_color = 1,1,.8,.1
# canvas to show highlighted line
canvas.after:
Color:
rgba: self.line_highlight_color
Rectangle:
size: self.width, self.line_height + dp(3)
pos: self.x, self.cursor_pos[1] - self.line_height -dp(1.5)
ScrollingBar:
bar_width: '12dp'
scroll_type: ['bars', 'content']
bar_inactive_color: 1,1,1,.4
bar_active_color: 1,1,1,.2
id: scroll
kv_lang_area: code_input
size_hint_x: None
width: '12dp'
on_scroll_start: root._do_cursor_scroll==False
on_scroll_move: root.do_bar_scroll(code_input, self)
on_scroll_stop: root._do_cursor_scroll==True
canvas.before:
Color:
rgba: code_input.background_color
Rectangle:
size: self.size
pos: self.pos
# canvas to show division
canvas.after:
Color:
rgba: (0, 0, 0, .4)
Line:
points: [self.x,self.y, self.x,self.height]
Color:
rgba: (0, 0, 0, .3)
Line:
points: [self.x-1,self.y, self.x-1,self.height]
Color:
rgba: (0, 0, 0, .2)
Line:
points: [self.x-2,self.y, self.x-2,self.height]
Color:
rgba: (0, 0, 0, .1)
Line:
points: [self.x-3,self.y, self.x-3,self.height]
width: 2
Widget:
size_hint_y: None
height: code_input.minimum_height
:
font_size: '13dp'
size_hint_y: None
color: self.normal_color
group: '_line_numbers_'
allow_no_selection: False
:
MenuButton:
on_release: root.copy()
MenuLabel:
text: 'Copy'
MenuLabel:
text: 'Ctrl+C'
type: 'shortcut'
MenuButton:
on_release: root.cut()
MenuLabel:
text: 'Cut'
MenuLabel:
text: 'Ctrl+X'
type: 'shortcut'
MenuButton:
on_release: root.paste()
MenuLabel:
text: 'Paste'
MenuLabel:
text: 'Ctrl+V'
type: 'shortcut'
:
size_hint: None,None
size: '230dp','40dp'
padding: '4dp'
canvas.before:
Color:
rgba: .2,.2,.2,1
RoundedRectangle:
size: self.size
pos: self.pos
radius: [dp(6),]
canvas.after:
Color:
rgba: .5,.5,.5,1
Line:
rounded_rectangle: [self.x,self.y,self.width,self.height,dp(6)]
SearchInput:
id: input
hint_text: 'search'
multiline: False
on_text_validate: root.find(input.text)
Label:
size_hint_x:None
width: self.texture_size[0]
id: found
IconLabelButton:
text: '%s' %icon('fa-arrow-left')
size_hint_x:None
width: '32dp'
on_release: root.find_prev(input.text)
color: 1,1,1
IconLabelButton:
text: '%s' %icon('fa-arrow-right')
size_hint_x: None
width: '32dp'
on_release: root.find_next(input.text)
color: 1,1,1
IconLabelButton:
text: '%s' %icon('fa-close')
width: '32dp'
size_hint_x:None
color: 1,1,1
on_release: root.dismiss()
================================================
FILE: kivystudio/widgets/codeinput/codeinput.py
================================================
'''
Code Input
==========
.. versionadded:: 1.5.0
.. image:: images/codeinput.jpg
.. note::
This widget requires ``pygments`` package to run. Install it with ``pip``.
The :class:`CodeInput` provides a box of editable highlighted text like the one
shown in the image.
It supports all the features provided by the :class:`~kivy.uix.textinput` as
well as code highlighting for `languages supported by pygments
`_ along with `KivyLexer` for
:mod:`kivy.lang` highlighting.
Usage example
-------------
To create a CodeInput with highlighting for `KV language`::
from kivy.uix.codeinput import CodeInput
from kivy.extras.highlight import KivyLexer
codeinput = CodeInput(lexer=KivyLexer())
To create a CodeInput with highlighting for `Cython`::
from kivy.uix.codeinput import CodeInput
from pygments.lexers import CythonLexer
codeinput = CodeInput(lexer=CythonLexer())
'''
__all__ = ('CodeInput', )
from pygments import highlight
from pygments import lexers
from pygments import styles
from pygments.formatters import BBCodeFormatter
from pygments.token import String
from kivy.uix.textinput import TextInput
from kivy.core.text.markup import MarkupLabel as Label
from kivy.cache import Cache
from kivy.properties import ObjectProperty, OptionProperty
from kivy.utils import get_hex_from_color, get_color_from_hex
from kivy.uix.behaviors import CodeNavigationBehavior
Cache_get = Cache.get
Cache_append = Cache.append
# TODO: color chooser for keywords/strings/...
class CodeInput(CodeNavigationBehavior, TextInput):
'''CodeInput class, used for displaying highlighted code.
'''
lexer = ObjectProperty(None)
'''This holds the selected Lexer used by pygments to highlight the code.
:attr:`lexer` is an :class:`~kivy.properties.ObjectProperty` and
defaults to `PythonLexer`.
'''
style_name = OptionProperty(
'default', options=list(styles.get_all_styles())
)
'''Name of the pygments style to use for formatting.
:attr:`style_name` is an :class:`~kivy.properties.OptionProperty`
and defaults to ``'default'``.
'''
style = ObjectProperty(None)
'''The pygments style object to use for formatting.
When ``style_name`` is set, this will be changed to the
corresponding style object.
:attr:`style` is a :class:`~kivy.properties.ObjectProperty` and
defaults to ``None``
'''
def __init__(self, **kwargs):
stylename = kwargs.get('style_name', 'default')
style = kwargs['style'] if 'style' in kwargs \
else styles.get_style_by_name(stylename)
self.formatter = BBCodeFormatter(style=style)
self.lexer = lexers.PythonLexer()
self.text_color = '#000000'
self._label_cached = Label()
self.use_text_color = True
super(CodeInput, self).__init__(**kwargs)
self._line_options = kw = self._get_line_options()
self._label_cached = Label(**kw)
# use text_color as foreground color
text_color = kwargs.get('foreground_color')
if text_color:
self.text_color = get_hex_from_color(text_color)
# set foreground to white to allow text colors to show
# use text_color as the default color in bbcodes
self.use_text_color = False
self.foreground_color = [1, 1, 1, .999]
if not kwargs.get('background_color'):
self.background_color = [.9, .92, .92, 1]
def on_style_name(self, *args):
self.style = styles.get_style_by_name(self.style_name)
self.background_color = get_color_from_hex(self.style.background_color)
self._trigger_refresh_text()
def on_style(self, *args):
self.formatter = BBCodeFormatter(style=self.style)
self._trigger_update_graphics()
def _create_line_label(self, text, hint=False):
# Create a label from a text, using line options
ntext = text.replace(u'\n', u'').replace(u'\t', u' ' * self.tab_width)
if self.password and not hint: # Don't replace hint_text with *
ntext = u'*' * len(ntext)
ntext = self._get_bbcode(ntext)
kw = self._get_line_options()
cid = u'{}\0{}\0{}'.format(ntext, self.password, kw)
texture = Cache_get('textinput.label', cid)
if texture is None:
# FIXME right now, we can't render very long line...
# if we move on "VBO" version as fallback, we won't need to
# do this.
# try to find the maximum text we can handle
label = Label(text=ntext, **kw)
if text.find(u'\n') > 0:
label.text = u''
else:
label.text = ntext
label.refresh()
# ok, we found it.
texture = label.texture
Cache_append('textinput.label', cid, texture)
label.text = ''
return texture
def _get_line_options(self):
kw = super(CodeInput, self)._get_line_options()
kw['markup'] = True
kw['valign'] = 'top'
kw['codeinput'] = repr(self.lexer)
return kw
def _check_doc_string(self):
text = self.text[:self.cursor_index()][::-1]
text = self.text
# print(self.text)
double = text.count('"""')
single = text.count("'''")
found_double = False
found_single = False
if double > 0 and double % 2 != 0:
found_double = True
# print('found a previous double')
if single > 0 and single % 2 != 0:
found_single = True
# print('found a previous single')
if found_double and found_single:
_doc_string = True
elif not found_double and not found_single:
_doc_string = False
else:
_doc_string = True
return _doc_string
def _get_text_width(self, text, tab_width, _label_cached):
# Return the width of a text, according to the current line options.
cid = u'{}\0{}\0{}'.format(text, self.password,
self._get_line_options())
width = Cache_get('textinput.width', cid)
if width is not None:
return width
lbl = self._create_line_label(text)
width = lbl.width
Cache_append('textinput.width', cid, width)
return width
def _get_bbcode(self, ntext):
# get bbcoded text for python
try:
ntext[0]
# replace brackets with special chars that aren't highlighted
# by pygment. can't use &bl; ... cause & is highlighted
ntext = ntext.replace(u'[', u'\x01').replace(u']', u'\x02')
ntext = highlight(ntext, self.lexer, self.formatter)
ntext = ntext.replace(u'\x01', u'&bl;').replace(u'\x02', u'&br;')
# replace special chars with &bl; and &br;
ntext = ''.join((u'[color=', str(self.text_color), u']',
ntext, u'[/color]'))
ntext = ntext.replace(u'\n', u'')
# # remove possible extra highlight options
# ntext = ntext.replace(u'[u]', '').replace(u'[/u]', '')
return ntext
except IndexError:
return ''
# overriden to prevent cursor position off screen
def _cursor_offset(self):
'''Get the cursor x offset on the current line
'''
offset = 0
try:
if self.cursor_col:
offset = self._get_text_width(
self._lines[self.cursor_row][:self.cursor_col])
return offset
except:
pass
finally:
return offset
def on_lexer(self, instance, value):
self._trigger_refresh_text()
def on_foreground_color(self, instance, text_color):
if not self.use_text_color:
self.use_text_color = True
return
self.text_color = get_hex_from_color(text_color)
self.use_text_color = False
self.foreground_color = (1, 1, 1, .999)
self._trigger_refresh_text()
if __name__ == '__main__':
from kivy.extras.highlight import KivyLexer
from kivy.app import App
class CodeInputTest(App):
def build(self):
return CodeInput(lexer=KivyLexer(),
font_size=12,
text='''
#:kivy 1.0
:
canvas:
Color:
rgb: .5, .5, .5
Rectangle:
pos: self.pos
size: self.size''')
CodeInputTest().run()
================================================
FILE: kivystudio/widgets/codeinput/styles/__init__.py
================================================
from .native_tweak import NativeTweakStyle
================================================
FILE: kivystudio/widgets/codeinput/styles/native_tweak.py
================================================
# -*- coding: utf-8 -*-
"""
pygments.styles.native
~~~~~~~~~~~~~~~~~~~~~~
pygments version of my "native" vim theme.
:copyright: Copyright 2006-2017 by the Pygments team, see AUTHORS.
:license: BSD, see LICENSE for details.
"""
from pygments.style import Style
from pygments.token import Keyword, Name, Comment, String, Error, \
Number, Operator, Generic, Token, Whitespace, Punctuation
class NativeTweakStyle(Style):
"""
Pygments version of the "native" vim theme.
"""
background_color = '#202020'
highlight_color = '#404040'
styles = {
Token: '#d0d0d0',
Whitespace: '#666666',
Comment: 'italic #777777',
Comment.Preproc: 'noitalic bold #cd2828',
Comment.Special: 'noitalic bold #e50808 bg:#520000',
Keyword: 'bold #CC6600',
Keyword.Pseudo: 'nobold',
Operator.Word: 'bold #CC6600',
Operator: '#CC6600',
String: '#FFCC00',
String.Other: '#ffa500',
Number: '#3677a9',
Name.Builtin: '#24909d',
Name.Variable: '#40ffff',
Name.Constant: '#40ffff',
Name.Class: 'underline #447fcf',
Name.Function: '#447fcf',
Name.Namespace: 'underline #447fcf',
Name.Exception: '#33CCFF',
Name.Tag: 'bold #CC6600',
Name.Attribute: '#bbbbbb',
Name.Decorator: '#ffa500',
Name.Builtin.Pseudo: '#33CCFF',
Generic.Heading: 'bold #ffffff',
Generic.Subheading: 'underline #ffffff',
Generic.Deleted: '#d22323',
Generic.Inserted: '#589819',
Generic.Error: '#d22323',
Generic.Emph: 'italic',
Generic.Strong: 'bold',
Generic.Prompt: '#aaaaaa',
Generic.Output: '#cccccc',
Generic.Traceback: '#d22323',
Error: 'bg:#FFFFFF #FFFFFF'
}
================================================
FILE: kivystudio/widgets/codeinput/tools.py
================================================
import re
def find_next(self, search, use_regex=False, case=False):
'''Find the next occurrence of the string according to the cursor
position
'''
text = self.text
if not case:
text = text.upper()
search = search.upper()
lines = text.splitlines()
col = self.cursor_col
row = self.cursor_row
found = -1
size = 0 # size of string before selection
line = None
search_size = len(search)
for i, line in enumerate(lines):
if i >= row:
if use_regex:
if i == row:
line_find = line[col + 1:]
else:
line_find = line[:]
found = re.search(search, line_find)
if found:
search_size = len(found.group(0))
found = found.start()
else:
found = -1
else:
# if on current line, consider col
if i == row:
found = line.find(search, col + 1)
else:
found = line.find(search)
# has found the string. found variable indicates the initial po
if found != -1:
self.cursor = (found, i)
break
size += len(line)
if found != -1:
pos = text.find(line) + found
self.select_text(pos, pos + search_size)
def find_prev(self, search, use_regex=False, case=False):
'''Find the previous occurrence of the string according to the cursor
position
'''
text = self.text
if not case:
text = text.upper()
search = search.upper()
lines = text.splitlines()
col = self.cursor_col
row = self.cursor_row
lines = lines[:row + 1]
lines.reverse()
line_number = len(lines)
found = -1
line = None
search_size = len(search)
for i, line in enumerate(lines):
i = line_number - i - 1
if use_regex:
if i == row:
line_find = line[:col]
else:
line_find = line[:]
found = re.search(search, line_find)
if found:
search_size = len(found.group(0))
found = found.start()
else:
found = -1
else:
# if on current line, consider col
if i == row:
found = line[:col].find(search)
else:
found = line.find(search)
# has found the string. found variable indicates the initial po
if found != -1:
self.cursor = (found, i)
break
if found != -1:
pos = text.find(line) + found
self.select_text(pos, pos + search_size)
================================================
FILE: kivystudio/widgets/dropdown.py
================================================
from kivystudio.behaviors import HoverBehavior
from kivy.uix.dropdown import DropDown
from kivy.properties import ObjectProperty, BooleanProperty
from kivy.lang import Builder
Builder.load_string('''
:
canvas.after:
Color:
rgba: 0,0,0,.6
Line:
rectangle: [self.x, self.y, self.width, self.height]
canvas.before:
BorderImage:
source: 'shadow32.png'
border: (26, 26, 26, 26)
size:(root.width + 68, root.height + 68)
pos: (root.x-36, root.y-36)
''')
class DropDownBase(HoverBehavior, DropDown):
''' Base widget for DropDown widget.
usually when mouse is right clicked'''
is_open = ObjectProperty(False)
auto_touch_dismiss = BooleanProperty(True)
def on_open(self):
self.is_open = True
def on_dismiss(self):
self.is_open = False
def on_touch_up(self, touch):
if self.collide_point(*touch.pos):
if self.auto_touch_dismiss:
self.dismiss()
return super(DropDownBase,self).on_touch_up(touch)
================================================
FILE: kivystudio/widgets/filemanager/LICENSE
================================================
The MIT License (MIT)
Copyright (c) 2019 Mahart Studios
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: kivystudio/widgets/filemanager/README.md
================================================
================
FileManager
================
A comprehensive file chooser intensively designed for the for desktop platform
The widget was originaly design and used for **kivy Studios **but is now available on kivy garden
#### KivyStudio
[](https://travis-ci.com/MichaelStott/KivMob)
[](https://badge.fury.io/py/kivmob)
[](https://www.python.org/downloads/release/python-270/)
[](https://pepy.tech/project/kivmob)
[](https://codeclimate.com/github/MichaelStott/KivMob/maintainability)
Simple method
```python
filemanager.open_file(path='.', callback=callback)
filemanager.save_file(path='.', callback=callback)
filemanager.choose_dir(path='.', callback=callback)
```
### Installation
```
garden install garden.filemanager
```
### Quickstart
```python
from kivy.uix.button import Button
from kivy.garden.filemanager import filemanager
def callback(path):
(path)
def open_file(*a):
filemanager.open_file(path='.', callback=callback)
btn = Button(text='Push Me')
btn.bind(on_release=open_file)
if __name__ == '__main__':
MyApp().run()
```
### FileManager being used in Kivy Studio
### FileManager Showcase
_Please contact us via pull request or project issue if you would like your app featured in this README and the documentation._
### Other
[Kivy]:
[KivyStudio]:
[Buildozer]:
Mahart Studio
[avour]:
================================================
FILE: kivystudio/widgets/filemanager/__init__.py
================================================
from .filechooserthumbview import StudioFileChooserThumbView
from kivy.uix.modalview import ModalView
from kivy.uix.behaviors import FocusBehavior
from kivy.uix.gridlayout import GridLayout
from kivy.uix.button import Button
from kivy.properties import ObjectProperty, OptionProperty
from kivy.factory import Factory
from kivy.core.window import Window
from kivy.utils import platform
from kivy.lang import Builder
import os
from functools import partial
from kivystudio.behaviors import HighlightBehavior
from kivystudio.widgets.splitter import StudioSplitter
file_path = os.path.dirname(__file__)
from kivy.resources import resource_add_path
resource_add_path(os.path.join(file_path, 'images'))
resource_add_path(os.path.join(file_path, 'file_formats'))
Builder.load_file(os.path.join(file_path,'filemanager.kv'))
# filepath_for_cool_icons ='/usr/share/icons/Vibrancy-Kali/apps/64'
class SideSelector_(HighlightBehavior, FocusBehavior, GridLayout):
pass
class SideButton_(Button):
def on_touch_down(self, touch):
if self.collide_point(*touch.pos):
self.parent.set_highlighted(self)
return super(SideButton_, self).on_touch_down(touch)
class FileManager(ModalView):
_dir_selector = ObjectProperty(None)
'''internal widget used to show and display the current dir path'''
_file_chooser = ObjectProperty(None)
''' the internal file chooser a subclass of FileChooserController'''
mode = OptionProperty('open_file', options=['open_file', 'save_file', 'choose_dir'])
__events__ = ('on_finished', )
def __init__(self, **k):
super(FileManager, self).__init__(**k)
self.new_bub = Factory.NewFolderBub_()
self.new_bub.ids.input.bind(on_text_validate=self.create_new_folder)
self._file_chooser.path = self.get_defualt_user_dir()
# self.mode = 'save_file'
def get_defualt_user_dir(self):
default_dir = None
if platform == 'linux':
username = os.getlogin()
if username != 'root':
default_dir = r'/home/%s'%username
else:
default_dir = r'/root'
elif platform == 'win':
base = os.getcwd().split("\\")[:3]
default_dir = "\\".join(base)
return default_dir
def set_side_panel_dir(self, name):
user_dir = self.get_defualt_user_dir()
self._file_chooser.path = os.path.join(user_dir, name)
def on_path(self, path):
path_list = path.split('/')
self._dir_selector.clear_widgets()
for i in path_list:
if i:
btn = Factory.DirButton_(size_hint=(None,1), text=str(i))
btn.bind(on_release=partial(self._go_dir_with_btn, btn))
if btn.width <= btn.texture_size[0]:
btn.width = btn.texture_size[0]+10
self._dir_selector.add_widget(btn)
else:
if i:
self.ids.dir_scroll.scroll_to(btn)
def _go_dir_with_btn(self, btn, *args):
path_list = []
children = self._dir_selector.children[:]
children.reverse()
for child in children:
path_list.append(child.text)
if child == btn:
break
self._file_chooser.path = '/'+ '/'.join(path_list)
def on_open(self):
Window.bind(on_key_down=self.handle_key)
def on_dismiss(self):
Window.unbind(on_key_down=self.handle_key)
# remove bubble if open
if self.new_bub in Window.children:
Window.remove_widget(self.new_bub)
def handle_key(self, keyboard, key, codepoint, text, modifier, *args):
if key == 8: # if user press the backspace reverse dir
if self.mode!='save_file' or (hasattr(self,'save_widget') \
and not(self.save_widget.ids.input.focus)):
self.reverse_dir()
elif key == 27:
self.handle_escape()
elif key == 13: # enter
pass
def reverse_dir(self):
previous_path = os.path.dirname(self._file_chooser.path)
if os.path.exists(previous_path):
self._file_chooser.path = previous_path
def on_mode(self, *args):
if hasattr(self, 'save_widget') and \
self.save_widget in self.ids.saving_container.children:
self.ids.saving_container.remove_widget(self.save_widget)
if hasattr(self, 'folder_btn') and \
self.folder_btn in self.ids.saving_container.children:
self.ids.saving_container.remove_widget(self.folder_btn)
if self.mode == 'save_file':
self.ids.title.text = 'Save file'
if not hasattr(self, 'save_widget'):
self.save_widget = Factory.SaveWidget_()
self.save_widget.ids.input.bind(on_text_validate=self.handle_saving)
func = lambda *args: self.handle_saving(self.save_widget.ids.input)
self.save_widget.ids.save_btn.bind(on_release=func)
self.ids.saving_container.add_widget(self.save_widget)
else:
if self.mode == 'open_file':
self.ids.title.text = 'Open file'
elif self.mode == 'choose_dir':
self.ids.title.text = 'Open folder'
if not hasattr(self, 'folder_btn'):
self.folder_btn = Factory.DirButton_(text='Select')
self.folder_btn.bind(on_release=self.folder_selected)
self.ids.saving_container.add_widget(self.folder_btn)
def folder_selected(self, *args):
path=self.ids.file_chooser.ids.stacklayout.current_highlighted_child.path
self.dispatch('on_finished', path)
def handle_escape(self):
if self.new_bub in Window.children:
Window.remove_widget(self.new_bub)
else:
self.dismiss()
def handle_bubble(self, btn):
if self.new_bub in Window.children:
Window.remove_widget(self.new_bub)
else:
self.new_bub.pos = (btn.x - (self.new_bub.width-btn.width), btn.y - self.new_bub.height)
Window.add_widget(self.new_bub)
def create_new_folder(self, textinput):
path = os.path.join(self._file_chooser.path, textinput.text)
if not os.path.exists(path):
print('making folder ', path)
try:
os.mkdir(path)
# a simple trick to force the file chooser to recompute the files
former_path = self._file_chooser.path
self._file_chooser.path = 'eeraef7h98fwb38rh3f8h23yr8i' # change to an invaid path
self._file_chooser.path = former_path # then change back
Window.remove_widget(self.new_bub)
except OSError:
print('error making dir')
else:
print('already exists')
def handle_saving(self, textinput):
path = os.path.join(self._file_chooser.path, textinput.text)
if not os.path.exists(path):
self.dispatch('on_finished', path)
else:
print('file already exist')
def file_selected(self, obj, path):
if self.mode == 'open_file':
self.dispatch('on_finished', path)
self.dismiss()
def on_finished(self, path):
self.on_selection([path])
self.dismiss()
def open_file(self, path='', on_selection=None):
self.mode = 'open_file'
self.on_selection = on_selection
self.open()
def save_file(self, path='', on_selection=None):
self.mode = 'save_file'
self.on_selection = on_selection
self.open()
def choose_dir(self, path='', on_selection=None):
self.mode = 'choose_dir'
self.on_selection = on_selection
self.open()
filemanager = FileManager()
if __name__ == "__main__":
from kivy.base import runTouchApp
from kivy.uix.button import Button
file_picker = FileManager()
btn = Button(text='push me')
btn.bind(on_release=lambda *args: file_picker.open())
runTouchApp(btn)
================================================
FILE: kivystudio/widgets/filemanager/filechooserthumbview/LICENSE
================================================
The MIT License (MIT)
Copyright (c) 2013-2014 Davide Depau
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: kivystudio/widgets/filemanager/filechooserthumbview/__init__.py
================================================
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""StudioFileChooserThumbView
====================
The StudioFileChooserThumbView widget is similar to FileChooserIconView,
but if possible it shows a thumbnail instead of a normal icon.
Usage
-----
You can set some properties in order to control its performance:
* **showthumbs:** Thumbnail limit. If set to a number > 0, it will show the
thumbnails only if the directory doesn't contain more files or directories.
If set to 0 it won't show any thumbnail. If set to a number < 0 it will always
show the thumbnails, regardless of how many items the current directory
contains. By default it is set to -1, so it will show all the thumbnails.
* **thumbdir:** Custom directory for the thumbnails. By default it uses
tempfile to generate it randomly.
* **thumbsize:** The size of the thumbnails. It defaults to 64d
"""
# Thanks to allan-simon for making the code more readable and less "spaghetti" :)
import os
from os.path import abspath, dirname
import mimetypes
#(enable for debugging)
import traceback
import shutil
import subprocess
from threading import Thread
from os.path import join, exists, dirname
from tempfile import mktemp, mkdtemp
from kivy.app import App
from kivy.lang import Builder
from kivy.metrics import dp
from kivy.utils import QueryDict
from kivy.properties import StringProperty
from kivy.properties import DictProperty
from kivy.properties import ObjectProperty
from kivy.properties import BooleanProperty
from kivy.properties import NumericProperty
from kivy.uix.filechooser import FileChooserController
from kivy.uix.behaviors import FocusBehavior
from kivy.uix.stacklayout import StackLayout
from kivy.uix.gridlayout import GridLayout
from kivystudio.behaviors import HighlightBehavior
# directory with this package
_path = os.path.dirname(os.path.realpath(__file__))
Builder.load_string("""
#: import Clock kivy.clock.Clock
:
stacklayout: stacklayout
on_entry_added: stacklayout.add_widget(args[1])
on_entries_cleared: stacklayout.clear_widgets()
scrollview: scrollview
ScrollView:
id: scrollview
FileStack_:
id: stacklayout
filechooser: root
width: scrollview.width
size_hint_y: None
height: self.minimum_height
spacing: '10dp'
padding: '10dp'
highlighted_shape: 'rounded_rectangle'
highlight_orientation: 'grid'
auto_scroll_to: True
on_size: Clock.schedule_once(lambda dt: setattr(self, 'grid_len', int(self.width/(self.children[0].width+10))), 1)
[StudioFileThumbEntry@IconWidget_]:
image: image
locked: False
path: ctx.path
selected: self.path in ctx.controller().selection
size_hint: None, None
cols: 1
size: ctx.controller().thumbsize + dp(52), self.minimum_height
on_double_tap: ctx.controller().entry_released(self, args[1])
canvas:
Color:
rgba: 1, 1, 1, 1 if self.selected else 0
BorderImage:
border: 8, 8, 8, 8
pos: root.pos
size: root.size
source: 'atlas://data/images/defaulttheme/filechooser_selected'
AsyncImage:
id: image
size_hint: 1, None
size: ctx.controller().thumbsize, ctx.controller().thumbsize
# pos: root.x + dp(24), root.y + dp(40)
Label:
size_hint: 1, None
text: ctx.name
text_size: (ctx.controller().thumbsize + dp(20), None)
halign: 'center'
size: ctx.controller().thumbsize + dp(10), self.texture_size[1]
color: 0,0,0,1
valign: 'top'
shorten_from: 'right'
:
""")
DEFAULT_THEME = 'atlas://data/images/defaulttheme/'
FILE_ICON = DEFAULT_THEME + 'filechooser_file'
FOLDER_ICON = DEFAULT_THEME + 'filechooser_folder'
UNKWON_ICON=DEFAULT_THEME + 'filechooser_file'
ICON_PATH = dirname(dirname(__file__)) + '/file_formats/'
MP3_ICON = ICON_PATH + 'music.png'
VIDEO_ICON = ICON_PATH + 'video.png'
PYTHON_ICON = ICON_PATH + 'python.png'
KV_ICON = ICON_PATH + 'kv.png'
JAVA_ICON = ICON_PATH + 'java.png'
PDF_ICON = ICON_PATH + 'pdf.png'
ARCHIVE_ICON = ICON_PATH + 'archive.png'
# UNKWON_ICON = '.png'
ARCHIVES_MIME = ('application/zip', 'application/x-tar',)
APK_MIME = 'application/vnd.android.package-archive'
EXE_MIME = 'application/x-msdos-program'
PDF_MIME = 'application/pdf'
##############################
FLAC_MIME = "audio/flac"
MP3_MIME = "audio/mpeg"
PYTHON_MIME = "text/x-python"
JAVA_MIME = "text/x-java"
AVCONV_BIN = 'avconv'
FFMPEG_BIN = 'ffmpeg'
CONVERT_BIN = 'convert'
class IconWidget_(GridLayout):
'Internal widget used to display files'
def __init__(self, **kwargs):
super(IconWidget_, self).__init__(**kwargs)
self.register_event_type('on_double_tap')
def on_touch_down(self, touch):
if self.collide_point(*touch.pos):
if touch.is_double_tap:
self.dispatch('on_double_tap', touch)
return True
else:
self.parent.set_highlighted(self)
return True
return super(IconWidget_, self).on_touch_down(touch)
def on_double_tap(self, touch):
if os.path.isfile(self.path):
self.parent.parent.parent.dispatch('on_file_select', self.path)
class FileStack_(HighlightBehavior, FocusBehavior, StackLayout):
# overiding enter from highlightbehavior
def do_enter(self):
new_path = self.current_highlighted_child.path
if os.path.isdir(new_path):
if new_path=='../': # move back
new_path = os.path.dirname(self.filechooser.path)
self.filechooser.path = new_path
elif os.path.isfile(new_path):
self.filechooser.dispatch('on_file_select', new_path)
class StudioFileChooserThumbView(FileChooserController):
'''Implementation of :class:`FileChooserController` using an icon view
with thumbnails.
'''
_ENTRY_TEMPLATE = 'StudioFileThumbEntry'
thumbdir = StringProperty(mkdtemp(prefix="kivy-", suffix="-thumbs"))
'''Custom directory for the thumbnails. By default it uses tempfile to
generate it randomly.
'''
showthumbs = NumericProperty(-1)
'''Thumbnail limit. If set to a number > 0, it will show the thumbnails
only if the directory doesn't contain more files or directories. If set
to 0 it won't show any thumbnail. If set to a number < 0 it will always
show the thumbnails, regardless of how many items the current directory
contains.
By default it is set to -1, so it will show all the thumbnails.
'''
thumbsize = NumericProperty(dp(64))
"""The size of the thumbnails. It defaults to 64dp.
"""
play_overlay = StringProperty(os.path.join(_path, 'play_overlay.png'))
"""Path to a PIL supported image file (e.g. png) that will be put over
videos thumbnail (e.g. a "play" button). If it's an empty string nothing
will happen.
Defaults to "".
"""
stacklayout = ObjectProperty(None)
filmstrip_left = StringProperty("")
filmstrip_right = StringProperty("")
_thumbs = DictProperty({})
scrollview = ObjectProperty(None)
def __init__(self, **kwargs):
super(StudioFileChooserThumbView, self).__init__(**kwargs)
self.register_event_type('on_file_select')
self.thumbnail_generator = ThreadedThumbnailGenerator()
if not exists(self.thumbdir):
os.mkdir(self.thumbdir)
def clear_cache(self, *args):
try:
shutil.rmtree(self.thumbdir, ignore_errors=True)
except:
traceback.print_exc()
def _dir_has_too_much_files(self, path):
if (self.showthumbs < 0):
return False
nbrFileInDir = len(
os.listdir(dirname(path))
)
return nbrFileInDir > self.showthumbs
def _create_entry_widget(self, ctx):
# instantiate the widget
widget = super(StudioFileChooserThumbView, self)._create_entry_widget(ctx)
kctx = QueryDict(ctx)
# default icon
widget.image.source = FOLDER_ICON if kctx.isdir else UNKWON_ICON
# schedule generation for later execution
self.thumbnail_generator.append(widget.image, kctx, self._get_image)
self.thumbnail_generator.run()
return widget
def _get_image(self, ctx):
try:
App.get_running_app().bind(on_stop=self.clear_cache)
except AttributeError:
pass
except:
traceback.print_exc()
if ctx.isdir:
return FOLDER_ICON
# if the directory contains more files
# than what has been configurated
# we directly return a default file icon
if self._dir_has_too_much_files(ctx.path):
return FILE_ICON
try:
mime = get_mime(ctx.name)
# if we already have generated the thumb
# for this file, we get it directly from our
# cache
if ctx.path in self._thumbs.keys():
return self._thumbs[ctx.path]
# if it's a picture, we don't need to do
# any transormation
if is_picture(mime, ctx.name):
return ctx.path
# for mp3/flac an image can be embedded
# into the file, so we try to get it
if mime == MP3_MIME:
return self._generate_image_from_mp3(
ctx.path
)
if mime == FLAC_MIME:
return self._generate_image_from_flac(
ctx.path
)
if mime == PYTHON_MIME:
return PYTHON_ICON
if mime == JAVA_MIME:
return JAVA_ICON
if mime in ARCHIVES_MIME:
return ARCHIVE_ICON
if mime == PDF_MIME:
return PDF_ICON
# if it's a video we will extract a frame out of it
if "video/" in mime:
return self._generate_image_from_video(ctx.path)
extention = os.path.splitext(ctx.name)[1]
if extention == '.kv':
return KV_ICON
except:
traceback.print_exc()
return FILE_ICON
return FILE_ICON
def _generate_image_from_flac(self, flacPath):
# if we don't have the python module to
# extract image from flac, we just return
# default file's icon
try:
from mutagen.flac import FLAC
except ImportError:
return FILE_ICON
try:
audio = FLAC(flacPath)
art = audio.pictures
return self._generate_image_from_art(
art,
flacPath
)
except(IndexError, TypeError):
return FILE_ICON
def _generate_image_from_mp3(self, mp3Path):
# if we don't have the python module to
# extract image from mp3, we just return
# default file's icon
try:
from mutagen.id3 import ID3
except ImportError:
return MP3_ICON
try:
audio = ID3(mp3Path)
art = audio.getall("APIC")
return self._generate_image_from_art(
art,
mp3Path
)
except(IndexError, TypeError):
return MP3_ICON
def _generate_image_from_art(self, art, path):
pix = pix_from_art(art)
ext = mimetypes.guess_extension(pix.mime)
if ext == 'jpe':
ext = 'jpg'
image = self._generate_image_from_data(
path,
ext,
pix.data
)
self._thumbs[path] = image
return image
def _gen_temp_file_name(self, extension):
return join(self.thumbdir, mktemp()) + extension
def _generate_image_from_data(self, path, extension, data):
# data contains the raw bytes
# we save it inside a file, and return this file's temporary path
image = self._gen_temp_file_name(extension)
with open(image, "w") as img:
img.write(data)
return image
def _generate_image_from_video(self, videoPath):
# we try to use an external software (avconv or ffmpeg)
# to get a frame as an image, otherwise => default file icon
data = extract_image_from_video(videoPath, self.thumbsize, self.play_overlay)
try:
if data:
return self._generate_image_from_data(
videoPath,
".png",
data)
else:
return VIDEO_ICON
except:
traceback.print_exc()
return VIDEO_ICON
def _gen_label(self, ctx):
size = ctx.get_nice_size()
temp = ""
try:
temp = os.path.splitext(ctx.name)[1][1:].upper()
except IndexError:
pass
if ctx.name.endswith(".tar.gz"):
temp = "TAR.GZ"
if ctx.name.endswith(".tar.bz2"):
temp = "TAR.BZ2"
if temp == "":
label = size
else:
label = size + " - " + temp
return label
def on_file_select(self, path):
pass
class ThreadedThumbnailGenerator(object):
"""
Class that runs thumbnail generators in a another thread and
asynchronously updates image widgets
"""
def __init__(self):
self.thumbnail_queue = []
self.thread = None
def append(self, widget, ctx, func):
self.thumbnail_queue.append([widget, ctx, func])
def run(self):
if self.thread is None or not self.thread.is_alive():
self.thread = Thread(target=self._loop)
self.thread.start()
def _loop(self):
while len(self.thumbnail_queue) != 0:
# call user function that generates the thumbnail
image, ctx, func = self.thumbnail_queue.pop(0)
image.source = func(ctx)
# test if the file is a supported picture
# file
def is_picture(mime, name):
if mime is None:
return False
return "image/" in mime and (
"jpeg" in mime or
"jpg" in mime or
"gif" in mime or
"png" in mime
) and not name.endswith(".jpe")
def pix_from_art(art):
pix = None
if len(art) == 1:
pix = art[0]
elif len(art) > 1:
for pic in art:
if pic.type == 3:
pix = pic
if not pix:
# This would raise an exception if no image is present,
# and the default one would be returned
pix = art[0]
return pix
def get_mime(fileName):
try:
mime = mimetypes.guess_type(fileName)[0]
if mime is None:
return ""
return mime
except TypeError:
return ""
return ""
def extract_image_from_video(path, size, play_overlay):
data = None
if exec_exists(AVCONV_BIN):
data = get_png_from_video(AVCONV_BIN, path, int(size), play_overlay)
elif exec_exists(FFMPEG_BIN):
data = get_png_from_video(FFMPEG_BIN, path, int(size), play_overlay)
return data
# generic function to call a software to extract a PNG
# from an video file, it return the raw bytes, not an
# image file
def get_png_from_video(software, video_path, size, play_overlay):
return subprocess.Popen(
[
software,
'-i',
video_path,
'-i',
play_overlay,
'-filter_complex',
'[0]scale=-1:' + str(size) + '[video],[1]scale=-1:' + str(size) + '[over],' +
'[video][over]overlay=(main_w-overlay_w)/2:(main_h-overlay_h)/2',
'-an',
'-vcodec',
'png',
'-vframes',
'1',
'-ss',
'00:00:01',
'-y',
'-f',
'rawvideo',
'-'
],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE
).communicate()[0]
def stack_images(software, bg, fg, out):
# You need ImageMagick to stack one image onto another
p = subprocess.Popen([software, bg, "-gravity", "Center", fg, "-compose", "Over", "-composite", out])
p.wait()
def exec_exists(bin):
try:
subprocess.check_output(["which", bin])
return True
except subprocess.CalledProcessError:
return False
except OSError:
return False
except:
return False
def compute_size(maxs, imgw, imgh):
if imgw > imgh:
return maxs, maxs*imgh/imgw
else:
return maxs*imgw/imgh, maxs
if __name__ == "__main__":
from kivy.base import runTouchApp
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.label import Label
box = BoxLayout(orientation="vertical")
fileChooser = StudioFileChooserThumbView(thumbsize=128)
label = Label(markup=True, size_hint_y=None)
fileChooser.mylabel = label
box.add_widget(fileChooser)
box.add_widget(label)
def setlabel(instance, value):
instance.mylabel.text = "[b]Selected:[/b] {0}".format(value)
fileChooser.bind(selection=setlabel)
runTouchApp(box)
================================================
FILE: kivystudio/widgets/filemanager/filemanager.kv
================================================
#: import win kivy.core.window.Window
#: import Clock kivy.clock.Clock
:
_dir_selector: dir_selector
_file_chooser: file_chooser
background: 'invisible.png'
background_color: 0,0,0,0
size_hint: None, None
size: win.width * 0.65, win.height * 0.7
auto_dismiss: False
on_open: _side_selector.width = dp(130)
canvas.before:
BorderImage:
source: 'shadow32.png'
border: (26, 26, 26, 26)
size:(root.width + 68, root.height + 68)
pos: (root.x-36, root.y-36)
BoxLayout:
orientation: 'vertical'
BoxLayout:
size_hint_y: None
height: '40dp'
canvas.before:
Color:
rgba: .8,.8,.8,1
RoundedRectangle:
size: self.size
pos: self.pos
radius: [(10.0, 10.0), (10.0, 10.0), (0, 0), (0, 0)]
Label:
id: title
color: 0,0,0,1
bold: True
ImageButton_:
size: '32dp', '32dp'
size_hint: None,None
pos_hint: {'center_y': .5, 'center_x': .5}
source: 'window_cancel.png'
on_release: root.dismiss()
BoxLayout:
SideSelector_:
id: _side_selector
size_hint_x: None
width: '130dp'
cols: 1
canvas.before:
Color:
rgba: .9,.9,.9,1
Rectangle:
size: self.size
pos: self.pos
SideButton_:
text: 'Home'
source: 'small_home.png'
on_release: file_chooser.path = root.get_defualt_user_dir()
SideButton_:
text: 'Documents'
source: 'small_document.png'
on_release: root.set_side_panel_dir(self.text)
SideButton_:
text: 'Downloads'
source: 'small_download.png'
on_release: root.set_side_panel_dir(self.text)
SideButton_:
text: 'Desktop'
source: 'small_folder.png'
on_release: root.set_side_panel_dir(self.text)
SideButton_:
text: 'Pictures'
source: 'small_picture.png'
on_release: root.set_side_panel_dir(self.text)
SideButton_:
text: 'Music'
source: 'small_music.png'
on_release: root.set_side_panel_dir(self.text)
SideButton_:
text: 'Videos'
source: 'small_video.png'
on_release: root.set_side_panel_dir(self.text)
StudioSplitter:
min_size: self.parent.width*0.7
max_size: max(self.parent.width*0.8, dp(130))
on_right:
self.right=self.parent.right;
_side_selector.width = self.parent.width-self.width
BoxLayout:
orientation: 'vertical'
canvas.before:
Color:
rgba: 1,1,1,1
Rectangle:
size: self.size
pos: self.pos
BoxLayout:
size_hint_y: None
height: '48dp'
ScrollView:
id: dir_scroll
canvas.after:
Color:
rgba: .5,.5,.5,.5
Line:
points: [self.x, self.y, self.right, self.y]
BoxLayout:
size_hint_x: None
width: self.minimum_width
padding: '6dp'
spacing: '4dp'
rows: 1
id: dir_selector
ImageButton_:
source: 'new_folder.png'
size: '40dp', '40dp'
size_hint: None,None
on_release: root.handle_bubble(self)
BoxLayout:
StudioFileChooserThumbView:
id: file_chooser
on_path: root.on_path(self.path)
on_file_select: root.file_selected(*args)
# progress_cls: 'Widget'
BoxLayout:
id: saving_container
size_hint_y: None
height: max(dp(10), self.minimum_height)
padding: '6dp'
canvas.before:
Color:
rgba: 1,1,1,1
RoundedRectangle:
size: self.size
pos: self.pos
radius: [(0, 0), (0, 0), (10.0, 10.0), (10.0, 10.0)]
:
background_normal: ''
background_color: .9,.9,.9,1
source: ''
color: .6,.6,.6,1
size_hint_y: None
height: '40dp'
BoxLayout:
size: root.size
pos: root.pos
Image:
size_hint_x: None
width: '40dp'
source: root.source
:
background_normal: ''
background_color: .9,.9,.9,1
color: 0,0,0,1
size_hint: (None, None)
width: self.texture_size[0] + dp(20)
height: '40dp'
dir_path: ''
:
background_normal: ''
source: ''
background_color: 0,0,0,0
Image:
size: root.size
pos: root.pos
source: root.source
:
size_hint: None,None
size: '180dp','90dp'
arrow_pos: 'top_right'
background_color: .6,.6,1,.8
on_parent: input.focus =True
BoxLayout:
padding: '5dp'
orientation: 'vertical'
Label:
text: 'Folder Name'
bold: True
TextInput:
id: input
size_hint_y: None
height: '36dp'
cursor_color: .2,.2,.2,1
multiline: False
:
size_hint_y: None
height: '60dp'
on_parent: input.focus =True
Label:
size_hint_x: None
width: self.texture_size[0] + dp(10)
text: 'Name'
bold: True
color: .3,.3,.3,1
TextInput:
id: input
size_hint_y: None
height: '36dp'
cursor_color: .2,.2,.2,1
multiline: False
pos_hint: {'center_y': .5}
text: 'Untitled-1'
Button:
id: save_btn
text: 'Save'
size_hint: None, None
background_normal: 'button.png'
background_color: .5,.5,.5,1
width: self.texture_size[0] + dp(16)
height: self.texture_size[1] + dp(10)
pos_hint: {'center_y': .5}
================================================
FILE: kivystudio/widgets/iconlabel.py
================================================
from kivy.uix.label import Label
from kivy.uix.behaviors import ToggleButtonBehavior, ButtonBehavior
from kivy import properties as prop
from kivy.lang import Builder
from kivystudio.behaviors import HoverBehavior, HoverInfoBehavior
class IconLabel(HoverInfoBehavior, Label):
icon = prop.StringProperty('')
icon_size = prop.NumericProperty(16)
def __init__(self, **k):
super(IconLabel, self).__init__(**k)
self.markup=True
class IconLabelButton(ButtonBehavior, IconLabel):
pass
class IconToggleLabel(ToggleButtonBehavior, IconLabel):
icon_down = prop.StringProperty()
icon_normal = prop.StringProperty()
def on_state(self, *args):
if self.state=='down':
self.icon = self.icon_down
else:
self.icon = self.icon_normal
Builder.load_string('''
:
text: '%s'%(icon(self.icon, self.icon_size)) if self.icon else ''
''')
================================================
FILE: kivystudio/widgets/rightclick_drop.py
================================================
from kivy.uix.boxlayout import BoxLayout
from kivy.core.window import Window
from kivy.lang import Builder
from kivystudio.tools import set_auto_mouse_position
class RightClickDrop(BoxLayout):
def __init__(self, **kwargs):
super(RightClickDrop, self).__init__(**kwargs)
def on_touch_down(self, touch):
if self.collide_point(*touch.pos): # touch should not unfocus input
pass
else:
self.dismiss()
return super(RightClickDrop,self).on_touch_down(touch)
def on_parent(self, *a):
if self.parent:
Window.bind(on_keyboard=self._on_keyboard)
else:
Window.unbind(on_keyboard=self._on_keyboard)
def _on_keyboard(self, instance, keycode, *args):
if keycode == 27: # on escape
self.dismiss()
return True
return True
def open(self):
if self not in Window.children:
set_auto_mouse_position(self)
Window.add_widget(self)
def dismiss(self):
if self in Window.children:
Window.remove_widget(self)
Builder.load_string('''
:
orientation: 'vertical'
size: '260dp',self.minimum_height
size_hint: None,None
canvas.before:
BorderImage:
source: 'shadow32.png'
border: (26, 26, 26, 26)
size:(root.width + 50, root.height + 50)
pos: (root.x-25, root.y-25)
''')
================================================
FILE: kivystudio/widgets/searchinput.py
================================================
from kivy.uix.textinput import TextInput
from kivy.lang import Builder
class SearchInput(TextInput):
pass
Builder.load_string('''
:
canvas.after:
Color:
rgba: self.line_color
Line:
rectangle: [self.x,self.y,self.width,self.height]
foreground_color: 1,1,1,1
background_color: .1,.1,.12,1
line_color: .8,.8,.8,0
hint_text_color: .6,.6,.6,1
cursor_color: 1,1,1,1
size_hint_y: None
height: self.minimum_height
on_focus:
if self.focus: self.line_color=.8,.8,.8,1
else: line_color=.8,.8,.8,0
''')
================================================
FILE: kivystudio/widgets/splitter.py
================================================
from kivy.uix.splitter import Splitter, SplitterStrip
from kivy.lang import Builder
from kivy.core.window import Window
from kivy.properties import BooleanProperty
from kivystudio.behaviors import HoverBehavior
Builder.load_string('''
#: import Factory kivy.factory.Factory
:
strip_cls: Factory.StudioSplitterStrip
strip_size: '4dp'
background_down: ''
background_normal: ''
background_color: .12,.12,.12,1
''')
class StudioSplitter(Splitter):
pass
class StudioSplitterStrip(HoverBehavior, SplitterStrip):
moving = BooleanProperty(False)
def on_hover(self, *args):
if self.hover:
Window.set_system_cursor('size_we')
else:
if not self.moving:
Window.set_system_cursor('arrow')
def on_press(self):
self.moving = True
def on_touch_up(self, touch):
if self.moving:
Window.set_system_cursor('arrow')
self.moving = False
return super(StudioSplitterStrip, self).on_touch_up(touch)
if __name__ == "__main__":
root = Builder.load_string('''
BoxLayout:
Button:
id: w1
size_hint_x: None
width: 200
StudioSplitter:
min_size: self.parent.width-200
max_size: max(self.parent.width*0.8, dp(130))
on_right: self.right=root.right; w1.width = root.width-self.width
Button:
text: 'Spit'
''')
from kivy.base import runTouchApp
runTouchApp(root)
================================================
FILE: kivystudio/widgets/tabbedpanel.py
================================================
from kivy.properties import ObjectProperty
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.tabbedpanel import (TabbedPanel,
TabbedPanelContent, TabbedPanelHeader)
class StudioPanelItem(TabbedPanelHeader):
''' Kivy Studio Panel Item for easy use,
acts exactly like the defualt kivy panelitem
just just has a custom header class so you can
specify you costume header class
'''
header_cls = ObjectProperty(None)
content = ObjectProperty(FloatLayout())
def __init__(self, **kwargs):
super(StudioPanelItem, self).__init__(**kwargs)
def add_widget(self, widget):
self.content.add_widget(widget)
def remove_widget(self, widget):
self.content.remove_widget(widget)
================================================
FILE: project/prjtTest1.htm
================================================
================================================
FILE: project/prjtTest1.js
================================================
================================================
FILE: repoTest1.js
================================================
================================================
FILE: repoTest2.htm
================================================
================================================
FILE: setup.py
================================================
'''
Setup.py
========
'''
from os.path import dirname, join, abspath
import io,os
try:
from setuptools import setup, find_packages
except ImportError:
from distutils.core import setup, find_packages
CURDIR = abspath(dirname(__file__))
with io.open(join(CURDIR, "README.md"), encoding="utf8") as fd:
README = fd.read()
with io.open(join(CURDIR, "LICENSE"), encoding="utf8") as fd:
LICENSE = fd.read()
def get_all_kv_files():
kv_files = []
for path,dirs,files in os.walk(CURDIR):
for f in files:
if os.path.splitext(f)[1]=='.kv':
kv_files.append(join(path,f))
return kv_files
setup(
name='kivystudio',
version='0.1.0',
description='A Software emulation tool for kivy applications',
long_description=README + u"\n\n" + LICENSE,
author='Mahart team and Contributors',
author_email='mahartstudio1@gmail.com',
# url='https://plyer.readthedocs.org/en/latest/',
install_requires=[
'kivy',
'pygments',
],
packages=find_packages(),
package_data={'': ['LICENSE', 'README.md'],
'kivystudio': [
'resources/*',
'widgets/filemanager/images/*',
'libs/resizablebehavior/*',
'components/screens/images/*']+get_all_kv_files()
},
package_dir={'kivystudio': 'kivystudio'},
include_package_data=True,
license=open(join(CURDIR, 'LICENSE')).read(),
zip_safe=False,
classifiers=[
'Development Status :: 1 - Beta',
'Intended Audience :: Developers',
'Natural Language :: English',
'License :: MIT License',
'Programming Language :: Python',
'Programming Language :: Python :: 2',
'Programming Language :: Python :: 3',
],
entry_points={
'console_scripts': [
'kivystudio=kivystudio.main:main',
]
}
)
================================================
FILE: tests/test_codeplace.py
================================================
from kivy.tests.common import GraphicUnitTest
from kivy.base import EventLoop
import sys
from os.path import dirname
sys.path.append(dirname(dirname(__file__)))
class CodePlaceTest(GraphicUnitTest):
def test_tabState(self):
from kivystudio.components.codeplace import CodePlace, get_tab_from_group
code_place = CodePlace()
filename1 = 'test_codeplace.py'
filename2 = 'test_codeplace1.py'
code_place.add_code_tab(filename=filename1)
code_place.add_code_tab(filename=filename2)
self.render(code_place)
# ensure widow safely
EventLoop.ensure_window()
tab1 = get_tab_from_group(filename1)
tab2 = get_tab_from_group(filename2)
self.assertEqual(tab1.state, 'normal')
self.assertEqual(tab2.state, 'down')
if __name__ == "__main__":
import unittest
unittest.main()
================================================
FILE: tests/test_emulator.py
================================================
from kivy.tests.common import GraphicUnitTest
from kivy.base import EventLoop
import sys
from os.path import dirname
sys.path.append(dirname(dirname(__file__)))
class EmulatorTest(GraphicUnitTest):
def setUp(self):
from kivystudio.parser import emulate_file
from kivystudio.components.emulator_area import emulator_area
EventLoop.ensure_window()
self.emulate_file = emulate_file
self.emulator_area = emulator_area
def test_emulation(self):
from kivy.uix.button import Button
self.emulate_file('/root/Pictures/test.py')
root_widget = self.emulator_area().screen_display.screen.root_widget
self.assertEqual(isinstance(root_widget, Button), True)
def test_changeScreen(self):
from kivy.uix.button import Button
self.emulate_file('/root/Pictures/test.py')
root_widget = self.emulator_area().screen_display.screen.root_widget
self.assertEqual(isinstance(root_widget, Button), True)
# then change screen
screen_display = self.emulator_area().screen_display
screen_display.screen_name = 'IpadScreen'
self.assertEqual(isinstance(root_widget, Button), True)
# change screen again
screen_display.screen_name = 'IphoneScreen'
self.assertEqual(isinstance(root_widget, Button), True)
def test_scaling(self):
self.emulate_file('/root/Pictures/test.py')
root_widget = self.emulator_area().screen_display.screen.root_widget
if __name__ == "__main__":
import unittest
unittest.main()
================================================
FILE: to-do.txt
================================================
To-Do List
* FilExplorer
* CodeInput Search
* Auto Suggestion in CodeInput
* Bottom Menu
* Custom terminal (Command Line)
* Splitter
* General Project Search (on side bar)
* When an image file is selected Show up an image tab
Bugs
Textinput
*tripple quote highlighting bug
*multiple indent doesn't work well
*Menus show the same thing and crashes
# Later
* Git Intergration
* Theming