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 [![Build Status](https://travis-ci.com/MichaelStott/KivMob.svg?branch=master)](https://travis-ci.com/MichaelStott/KivMob) [![Python](https://img.shields.io/badge/python-2-green.svg)](https://www.python.org/downloads/release/python-270/) [![Python](https://img.shields.io/badge/python-3-green.svg)](https://www.python.org/downloads/release/python-270/) [![Downloads](https://pepy.tech/badge/kivmob)](https://pepy.tech/project/kivmob) [![Maintainability](https://api.codeclimate.com/v1/badges/add8cd9bd9600d898b79/maintainability)](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**. ![ScreenShot](https://raw.github.com/kivy-garden/garden.resizable_behavior/master/doc/screenshot.png) ## 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 ================================================ ![Screenshot](https://github.com/jeysonmc/garden.iconfonts/blob/master/screenshot.png "Scrennshot") 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 [![Build Status](https://travis-ci.com/MichaelStott/KivMob.svg?branch=master)](https://travis-ci.com/MichaelStott/KivMob) [![PyPI version](https://badge.fury.io/py/kivmob.svg)](https://badge.fury.io/py/kivmob) [![Python](https://img.shields.io/badge/python-2.7-green.svg)](https://www.python.org/downloads/release/python-270/) [![Downloads](https://pepy.tech/badge/kivmob)](https://pepy.tech/project/kivmob) [![Maintainability](https://api.codeclimate.com/v1/badges/add8cd9bd9600d898b79/maintainability)](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