class OscServer:
class OscClient:
    def __init__(self,ip=None,port=7001):
    def start(self):
    def stop(self):
    def osc_value(self,msg):
    def find_num_in_addr(self,addr):
    def map_unique(self,addr,fun):
    def get_ip_addr(self):
    def __init__(self,ip="127.0.0.1",port=7000):
    def build_msg(self,addr,arg):
    def build_n_send(self,addr,arg):
    def build_n_send_bundle(self,addr,argz):
            IP = s.getsockname()[0]
            IP = '127.0.0.1' C = GlobalConfig()
class Database:
class Search:
class ClipSearch(Search):
class TagDB(Search):
class FileOPs:
class FileHierarchyNode:
class FileHierarchy:
    def __init__(self):
    def last_search(self):
    def alphabetical_listing(self):
    def hierarchical_listing(self):
        def traverse(node):
    def all_tags(self):
    def add_clip(self, clip):
    def init_a_clip(self, clip):
    def add_a_clip(self, clip):
    def remove_clip(self, clip):
    def rename_clip(self, clip, new_name):
    def move_clip(self, clip, new_fname, new_name=None):
    def clear(self):
    def __init__(self):
    def refresh(self):
    def add_thing(self, name, thing):
    def remove_thing(self, name, thing):
    def search_by_prefix(self, prefix, n=-1):
    def __init__(self, clips):
    def clear(self):
    def add_clip(self, clip):
    def remove_clip(self, clip):
    def search(self, search_term):
    def refresh(self):
    def __init__(self):
    def add_clip(self, clip):
    def add_tag(self, tag, clip):
    def add_tag_to_clip(self, tag, clip):
    def update_clip_tags(self, tag_list, clip):
    def remove_clip(self, clip):
    def remove_tag(self, tag, clip):
    def remove_tag_from_clip(self, tag, clip):
    def search(self, search_term):
    def refresh(self):
    def clear(self):
    def __init__(self):
    def update_last_save(self, filename):
    def save_settings(self, settings, name="settings"):
    def load_settings(self, settings_el):
    def pretty_print(self, el):
    def save_clip(self, clip):
    def load_clip(self, clip_element):
    def save_clip_col(self, clip_col):
    def load_clip_col(self, col_element, clips_from_db):
    def save_clip_storage(self, clip_store):
    def load_clip_storage(self, clip_storage_el, clips_from_db):
    def save_database(self, database):
    def load_database(self, database_el, database):
    def save_midi(self, midi_key_list):
    def load_midi(self):
    def create_save(self, name):
    def create_load(self, filename):
    def __init__(self,name,what,data=None):
    def find_name_in_children(self,search_name):
    def __str__(self,prefix=""):
    def __init__(self):
    def add_clip(self,clip,cur_node=None,tail=None):
    def splitpath(self, path, maxdepth=20):
    def check_ignore(self,path_list): class Clip:
class ClipCollection:
    def __init__(self, filepath, activation_string, name=None,
                 thumbnail_path=None, params={}, tags=[]):
    def add_tag(self, tag):
    def str_tags(self):
    def remove_tag(self, tag):
    def __lt__(self, other):
    def __gt__(self, other):
    def __str__(self):
    def __init__(self, n=8, name='new_col'):
    def __getitem__(self, i):
    def __setitem__(self, i, clip):
    def __len__(self):
    def index(self, what):
    def safe_extend(self, i): class SetupGui:
    def __init__(self, rootwin=None, parent=None):
        self.rootwin, self.parent = rootwin, parent
        self.root_frame = tk.Toplevel(takefocus=True)
        self.root_frame.title('setup')
        self.root_frame.focus_force()

        self.menubar = tk.Menu(self.root_frame)
        self.menubar.add_command(
            label="undo all changes", command=self.undo_all)
        self.menubar.add_command(
            label="load defaults", command=self.reset_default)

        self.root_frame.config(menu=self.menubar)

        if self.rootwin is not None:
            self.rootwin.withdraw()
            self.major_changes = []

            def close_fun(*args):
                self.close()
                self.rootwin.destroy()
        else:
            self.parent.root.call('wm', 'attributes', '.', '-topmost', '0')
            # baddies
            self.major_changes = ['NO_LAYERS', 'NO_Q', 'NO_LP', 'MODEL_SELECT',
                                  'OSC_PORT', 'MTGUI_ENABLED', 'MTGUI_IP_ADDR',
                                  'XTERNAL_PLAYER_SELECT', 'SELECTED_THEME']

            def close_fun(*args):
                self.close()
                self.parent.setup_gui = None
                self.parent.toggle_on_top()

        self.root_frame.protocol("WM_DELETE_WINDOW", close_fun)

        # self.generate_font_measurements()

        self.instruction_to_fun = {
            'folder_select': self.add_folder_select,
            'file_select': self.add_file_select,
            'int_choice': self.add_int_choice,
            'bool_choice': self.add_bool_choice,
            'float_choice': self.add_float_choice,
            'list_choice': self.add_list_choice,
            'list_enter': self.add_list_enter,
            'str_enter': self.add_str_enter,
        }

        self.name_to_var = {}
        self.name_to_frame = {}

        self.config_book = ttk.Notebook(self.root_frame)
        self.config_book.pack(expand=True, fill=tk.BOTH)

        # generate theme info
        sol_theme_path = os.path.dirname(sol.themes.__file__)
        self.theme_names = [name for _, name, _ in pkgutil.iter_modules([sol_theme_path])]

        # tabs
        self.reset()
        self.generate_font_measurements()

    def reset(self, to_default=False):

        if to_default:
            C.load()

        cur_no_tabs = len(self.config_book.tabs())
        reopen_tab = -1

        if cur_no_tabs > 0:
            cur_tab_id = self.config_book.select()
            reopen_tab = self.config_book.index(cur_tab_id)
            for _ in range(cur_no_tabs):
                self.config_book.forget(0)

        self.param_tab = None
        self.video_tab = None
        self.gui_tab = None

        tab_padding = '5 5 5 5'

        self.param_tab = ttk.Frame(self.root_frame, padding=tab_padding)
        self.network_tab = ttk.Frame(self.root_frame, padding=tab_padding)
        self.video_tab = ttk.Frame(self.root_frame, padding=tab_padding)
        self.gui_tab = ttk.Frame(self.root_frame, padding=tab_padding)

        for tab_name in [(self.param_tab, 'sol config'),
                         (self.network_tab, 'network config'),
                         (self.video_tab, 'video player'),
                         (self.gui_tab, 'gui config')]:
            self.config_book.add(tab_name[0], text=tab_name[1])

        # construction instructions

        # instruction sets are (instruction, hint text, variable name, [any
        # extra variables])
        param_tab_instr = [
            ('label_frame', 'default folders', '', []),
            ('folder_select', 'savedata directory', 'SAVEDATA_DIR', []),
            ('folder_select', 'screenshot directory', 'SCROT_DIR', []),
            ('label_frame', 'sol parameters', '', []),
            ('int_choice', '# of layers', 'NO_LAYERS', []),
            ('int_choice', '# of cue points', 'NO_Q', []),
            ('int_choice', '# of loop ranges', 'NO_LP', []),
            ('list_enter', 'ignored directories', 'IGNORED_DIRS', []),
            ('bool_choice', 'print debug info', 'DEBUG', []),
            ('label_frame', 'midi options', '', []),
            ('bool_choice', 'midi enabled', 'MIDI_ENABLED', []),
            ('bool_choice', 'separate keys for cue/loop points', 'SEPARATE_QP_LP', []),
            ('bool_choice', 'separate keys for de/activation', 'SEPARATE_DELETE', []),
            ('float_choice', 'default control sensitivity', 'DEFAULT_SENSITIVITY', []),
        ]

        network_tab_instr = [
            ('label_frame', 'server config', '', []),
            ('int_choice', 'osc port', 'OSC_PORT', []),
            ('label_frame', 'multitouch client', '', []),
            ('bool_choice', 'enabled', 'MTGUI_ENABLED', []),
            ('str_enter', 'receiving ip', 'MTGUI_IP_ADDR', [])
        ]

        video_tab_instr = [
            ('label_frame', 'video software config', '', []),
            ('list_choice', 'vj software', 'MODEL_SELECT', C.MODEL_SELECT_OPTIONS),
            ('folder_select', 'composition directory', 'RESOLUME_SAVE_DIR', []),
            ('list_enter', 'supported filetypes', 'SUPPORTED_FILETYPES', []),
            ('label_frame', 'external player config', '', []),
            ('list_choice', 'external player', 'XTERNAL_PLAYER_SELECT',
             C.EXTERNAL_PLAYER_SELECT_OPTIONS),
            ('file_select', 'mpv script', 'MEMEPV_SCRIPT_PATH', []),
            ('str_enter', 'external command', 'EXTERNAL_PLAYER_COMMAND', []),
            ('label_frame', 'ffmpeg options', '', []),
            ('folder_select', 'ffmpeg directory (leave blank if in path)',
             'FFMPEG_PATH', []),
            ('int_choice', '# of thumbnails to generate', 'NO_FRAMES', []),
            ('int_choice', 'thumbnail width', 'THUMBNAIL_WIDTH', []),
        ]

        gui_tab_instr = [
            ('label_frame', 'sol options', '', []),
            ('list_choice', 'theme', 'SELECTED_THEME', self.theme_names),
            ('bool_choice', 'always on top', 'ALWAYS_ON_TOP', []),
            ('label_frame', 'thumbnail options', '', []),
            ('int_choice', 'display width', 'THUMB_W', []),
            ('int_choice', 'hover refresh interval (ms)', 'REFRESH_INTERVAL', []),
        ]

        self.compile_config_page(param_tab_instr, self.param_tab)
        self.compile_config_page(network_tab_instr, self.network_tab)
        self.compile_config_page(video_tab_instr, self.video_tab)
        self.compile_config_page(gui_tab_instr, self.gui_tab)

        self.name_to_var['NO_LP'][0].trace('w', self.loop_lim_check)

        if reopen_tab >= 0:
            self.config_book.select(self.config_book.tabs()[reopen_tab])

        self.root_frame.update_idletasks()
        self.root_frame.after_idle(
            lambda: self.root_frame.minsize(max(500, self.root_frame.winfo_width()),
                                            self.root_frame.winfo_height()))

    def undo_all(self):
        self.reset()

    def reset_default(self):
        self.reset(True)

    def close(self):

        type_to_fun = {
            'int': int,
            'bool': bool,
            'str': str,
            'float': float,
            'list': lambda sl: [s.strip() for s in sl.split(',')]
        }

        any_major = False

        for k, (v_var, v_type) in self.name_to_var.items():
            try:
                new_val = type_to_fun[v_type](v_var.get())
                if k in self.major_changes:
                    if C.dict[k] != new_val:
                        any_major = True
                C.dict[k] = new_val
            except:
                pass

        C.save()
        if any_major:
            tkmb.showwarning(
                '', 'you may have to restart sol\nfor your changes to take effect')
        self.root_frame.destroy()

    def generate_font_measurements(self):
        font = tkFont.Font()
        # height
        C.dict['FONT_HEIGHT'] = font.metrics("linespace")
        # measure font widths
        char_widths = {}
        for c in string.printable:
            char_widths[c] = font.measure(c)

        if 'FONT_WIDTHS' in C.dict:
            for k, v in char_widths.items():
                C.dict['FONT_WIDTHS'][k] = v
        else:
            C.dict['FONT_WIDTHS'] = char_widths

        count, running_sum = 0, 0
        for _, v in C.dict['FONT_WIDTHS'].items():
            count += 1
            running_sum += v
        C.dict['FONT_AVG_WIDTH'] = running_sum / count

    def hide_unhide(self, selection, var_names):
        keys_we_want = []
        for k in self.name_to_frame.keys():
            if '_' in k:
                if any([v in k for v in var_names]):
                    keys_we_want.append(k)

        for k in keys_we_want:
            if selection in k:
                self.name_to_frame[k].pack(
                    side=tk.TOP, expand=False, fill=tk.X, anchor='n')
            else:
                self.name_to_frame[k].pack_forget()

    def compile_config_page(self, instruction_set, parent_tab):
        last_label_frame = None
        starting_optionals = []
        for instruction in instruction_set:
            instr_type, instr_text, instr_varn, instr_extr = instruction

            if instr_type == 'label_frame':
                last_label_frame = self.add_label_frame(instr_text, parent_tab)

            elif instr_type in self.instruction_to_fun:
                starting_choice = None
                if instr_varn in C.dict:
                    starting_choice = C.dict[instr_varn]

                if last_label_frame is not None:
                    new_var, var_type, new_frame = self.instruction_to_fun[instr_type](
                        instr_text, last_label_frame, starting_choice, instr_extr)
                    self.name_to_var[instr_varn] = (new_var, var_type)
                    self.name_to_frame[instr_varn] = new_frame

                    if instr_type == 'list_choice':
                        starting_optionals.append(
                            (starting_choice, instr_extr))
        for sop in starting_optionals:
            # print(sop)
            self.hide_unhide(*sop)

    #######################
    # COMPILER HELPER FUNS

    def add_label_frame(self, frame_name, parent_tab):
        new_label_frame = ttk.LabelFrame(
            parent_tab, text=frame_name, padding='5 5 5 5')
        new_label_frame.pack(side=tk.TOP, expand=False, fill=tk.X, anchor='n')
        return new_label_frame

    def add_choice_row(self, parent_frame, hint_text):
        new_frame = ttk.Frame(parent_frame)
        new_frame.pack(fill=tk.X)
        desc_label = ttk.Label(new_frame, text='{} :'.format(
            hint_text), anchor='w', padding='0 5 0 5')
        desc_label.pack(side=tk.LEFT)
        return new_frame

    def add_folder_select(self, hint_text, parent_frame, starting_choice=None, extra_args=None):
        new_frame = self.add_choice_row(parent_frame, hint_text)
        new_var = tk.StringVar()

        if starting_choice is not None:
            new_var.set(str(starting_choice))

        def change_folder():
            ask_fun = tkfd.askdirectory
            new_folder_path = ask_fun(
                parent=parent_frame, title='select folder', mustexist=True)
            if new_folder_path:
                new_folder_path = os.sep.join(new_folder_path.split('/'))
                new_var.set(new_folder_path)

        dot_but = ttk.Button(new_frame, text='..', command=change_folder, width=1, takefocus=False)
        dot_but.pack(side=tk.RIGHT, anchor='e')

        current_path_label = ttk.Label(
            new_frame, textvar=new_var, anchor='w', relief='sunken')
        current_path_label.pack(side=tk.RIGHT, fill=tk.X,
                                anchor='e', expand=True)

        return new_var, 'str', new_frame

    def add_file_select(self, hint_text, parent_frame, starting_choice=None, extra_args=None):
        new_frame = self.add_choice_row(parent_frame, hint_text)
        new_var = tk.StringVar()

        if starting_choice is not None:
            new_var.set(str(starting_choice))

        def change_file():
            ask_fun = tkfd.askopenfilename
            new_file_path = ask_fun(parent=parent_frame, title='select file')
            if new_file_path:
                new_file_path = os.sep.join(new_file_path.split('/'))
                new_var.set(new_file_path)

        dot_but = ttk.Button(new_frame, text='..', command=change_file, width=1, takefocus=False)
        dot_but.pack(side=tk.RIGHT, anchor='e')

        current_path_label = ttk.Label(
            new_frame, textvar=new_var, anchor='w', relief='sunken')
        current_path_label.pack(side=tk.RIGHT, fill=tk.X,
                                anchor='e', expand=True)

        return new_var, 'str', new_frame

    def add_int_choice(self, hint_text, parent_frame, starting_choice=None, extra_args=None):
        new_frame = self.add_choice_row(parent_frame, hint_text)
        new_var = tk.StringVar()

        if starting_choice is not None:
            new_var.set(str(starting_choice))

        no_entry = tk.Spinbox(new_frame, from_=0, to=9999,
                              textvariable=new_var, justify='left', width=5)
        no_entry.pack(side=tk.RIGHT, anchor='e')

        return new_var, 'int', new_frame

    def add_float_choice(self, hint_text, parent_frame, starting_choice=None, extra_args=None):
        new_frame = self.add_choice_row(parent_frame, hint_text)
        new_var = tk.StringVar()

        if starting_choice is not None:
            new_var.set(str(starting_choice))

        no_entry = tk.Spinbox(new_frame, from_=0, to=2, increment=0.005,
                              textvariable=new_var, justify=tk.RIGHT, width=5)
        no_entry.pack(side=tk.RIGHT, anchor='e')

        return new_var, 'float', new_frame

    def add_bool_choice(self, hint_text, parent_frame, starting_choice=None, extra_args=None):
        new_frame = self.add_choice_row(parent_frame, hint_text)
        new_var = tk.IntVar()

        if starting_choice is not None:
            new_var.set(int(starting_choice))

        check_but = ttk.Checkbutton(new_frame, variable=new_var, takefocus=False)
        check_but.pack(side=tk.RIGHT, anchor='e')

        return new_var, 'bool', new_frame

    def add_list_choice(self, hint_text, parent_frame, starting_choice=None, extra_args=None):
        new_frame = self.add_choice_row(parent_frame, hint_text)
        new_var = tk.StringVar()
        pos_values = ' '.join(extra_args)
        selector = ttk.Combobox(new_frame, textvariable=new_var, values=pos_values)
        selector.config(state='readonly')
        selector.pack(side=tk.RIGHT, anchor='e')

        if starting_choice is not None:
            new_var.set(str(starting_choice))

        def gen_hide_callback():
            dis_var = new_var
            x_args = extra_args

            def callback(*args):
                self.hide_unhide(dis_var.get(), x_args)
            return callback

        hide_cb = gen_hide_callback()
        new_var.trace("w", hide_cb)

        return new_var, 'str', new_frame

    def add_list_enter(self, hint_text, parent_frame, starting_choice=None, extra_args=None):
        new_frame = self.add_choice_row(parent_frame, hint_text)
        new_var = tk.StringVar()
        list_entry = ttk.Entry(new_frame, textvariable=new_var, justify="left")
        list_entry.pack(side=tk.RIGHT, fill=tk.X, anchor='e', expand=True)

        if starting_choice is not None:
            starting_text = ", ".join(starting_choice)
            new_var.set(starting_text)

        return new_var, 'list', new_frame

    def add_str_enter(self, hint_text, parent_frame, starting_choice=None, extra_args=None):
        new_frame = self.add_choice_row(parent_frame, hint_text)
        new_var = tk.StringVar()

        if starting_choice is not None:
            new_var.set(str(starting_choice))

        str_entry = ttk.Entry(new_frame, textvariable=new_var, justify="left")
        str_entry.pack(side=tk.RIGHT, fill=tk.X, anchor='e', expand=True)

        return new_var, 'str', new_frame

    ################
    # EXTRA CHECKS

    def loop_lim_check(self, *args):
        try:
            no_lp, no_qp = int(self.name_to_var['NO_LP'][0].get()), int(self.name_to_var['NO_Q'][0].get())
            if no_lp > no_qp:
                self.name_to_var['NO_LP'][0].set(no_qp)
        except:
            pass class GlobalConfig:
    def __init__(self, load_defaults=False):
        self.dict = self.__dict__

        self.root_path = os.path.expanduser("~/.sol")
        if not os.path.exists(self.root_path):
            os.mkdir(self.root_path)

        self.default_options = {
            # sol params
            'DEBUG'					: True,
            'SAVEDATA_DIR'			: './savedata',
            'SCROT_DIR'				: './scrot',
            'NO_LAYERS'				: 2,
            'NO_Q'					: 8,
            'NO_LP'					: 8,
            'IGNORED_DIRS'			: ['C:\\', 'dxv', 'VJ'],

            # midi things
            'MIDI_ENABLED'          : True,
            'DEFAULT_SENSITIVITY'   : 0.005,  # control hacks
            'SEPARATE_QP_LP'        : False,
            'SEPARATE_DELETE'       : False,

            # network params
            'OSC_PORT'			    : 7001,
            'MTGUI_ENABLED'		    : False,
            'MTGUI_IP_ADDR'		    : '127.0.0.1',

            # model params
            'MODEL_SELECT'						: 'RESOLUME',
            'MODEL_SELECT_OPTIONS'				: ['RESOLUME', 'MPV', 'ISADORA'],
            'RESOLUME_SAVE_DIR'					: '',
            'SUPPORTED_FILETYPES'				: ['.mov', '.webm', '.mp4', '.avi'],
            'XTERNAL_PLAYER_SELECT'				: 'EXTERNAL_PLAYER',
            'EXTERNAL_PLAYER_SELECT_OPTIONS'	: ['MEMEPV', 'EXTERNAL_PLAYER'],
            'MEMEPV_SCRIPT_PATH'				: 'C:\\code\\vj\\memepv\\test.js',
            'EXTERNAL_PLAYER_COMMAND'			: '',

            # gui params
            'ALWAYS_ON_TOP' 	: True,
            'THUMB_W' 			: 192,
            'REFRESH_INTERVAL'  : 200,
            'FFMPEG_PATH'		: '',
            'NO_FRAMES'			: 5,
            'THUMBNAIL_WIDTH'	: 192,
            'FONT_WIDTHS'		: {},
            'FONT_HEIGHT'		: 10,
            'FONT_AVG_WIDTH'	: 5,
            'SELECTED_THEME'    : 'dark_like_my_soul',
        }

        self.default_options['RESOLUME_SAVE_DIR'] = os.path.expanduser(
            os.sep.join(['~', 'Documents', 'Resolume Arena 5', 'compositions']))
        self.default_options['SAVEDATA_DIR'] = os.path.join(
            self.root_path, 'savedata')
        self.default_options['SCROT_DIR'] = os.path.join(
            self.root_path, 'scrot')

        self.config_savefile = os.path.join(
            self.default_options['SAVEDATA_DIR'], 'config.pickle')

        if load_defaults:
            self.load()
        else:
            self.load(self.config_savefile)

        check_folders = ['SAVEDATA_DIR', 'SCROT_DIR']
        for cf in check_folders:
            folder = self.__dict__[cf]
            if not os.path.exists(folder):
                os.mkdir(folder)

        self.setup_theme()

    def setup_theme(self):
        self.themer = themer
        theme_package_name = 'sol.themes.' + self.dict['SELECTED_THEME']
        self.CURRENT_THEME = importlib.import_module(theme_package_name)

    def save(self):
        del self.themer
        del self.CURRENT_THEME
        try:
            with open(self.config_savefile, 'wb') as save_handle:
                pickle.dump(self.dict, save_handle,
                            protocol=pickle.HIGHEST_PROTOCOL)
            if self.DEBUG:
                print('saved', self.config_savefile)
        except Exception as e:
            print(e)
            if self.DEBUG:
                print('failed to make config savedata')
        self.setup_theme()

    def load(self, load_file=None):
        defaults = self.default_options.items()
        if load_file is not None and os.path.exists(load_file):
            try:
                with open(load_file, 'rb') as save_handle:
                    load_dict = pickle.load(save_handle)
                self.__dict__ = load_dict
            except:
                if self.DEBUG:
                    print('failed to load config savedata\nloading defaults...')

        for k, v in defaults:
            if k not in self.dict:
                self.dict[k] = v class RenameWin(ChildWin):
    def __init__(self, parent, clip, callback):
        super(RenameWin, self).__init__(parent, 'rename', 0.45, 0.25)
        self.clip = clip
        self.callback = callback
        self.fname_var = tk.StringVar()

        self.setup_ok_cancel()

        self.entry_frame = ttk.Frame(self.root_frame)
        self.entry_frame.pack(side=tk.TOP, expand=True, fill=tk.X, anchor=tk.S)

        rest_of_path, start_f = os.path.split(self.clip.f_name)
        dot_i = start_f.rfind('.')
        self.start_name, ext = start_f[:dot_i], start_f[dot_i:]

        self.fname_var.set(self.start_name)

        self.text_entry = ttk.Entry(self.entry_frame, textvariable=self.fname_var, justify="right")
        self.text_entry.pack(side=tk.LEFT, expand=True, fill=tk.X, anchor=tk.S)

        ext_label = ttk.Entry(self.entry_frame, justify="left")
        ext_label.insert(0, ext)
        ext_label.pack(side=tk.LEFT, anchor=tk.S)
        ext_label.config(state='disabled')

        self.format_return = rest_of_path + os.sep + '{}' + ext

        self.text_entry.focus()
        self.text_entry.selection_range(0, tk.END)
        self.text_entry.icursor(tk.END)

    def ok(self, *args):
        new_fname = self.fname_var.get()
        if len(new_fname) == 0:
            return
        if new_fname == self.start_name:
            self.close()
        self.callback(self.format_return.format(new_fname), new_fname)
        self.close()

    def close(self, *args):
        super(RenameWin, self).close() class MoveWin(ChildWin):
    def __init__(self, parent, clip, callback):
        super(MoveWin, self).__init__(parent, 'move', 0.5, 0.5)
        self.parent = parent
        self.clip = clip
        self.callback = callback
        self.top_frame = ttk.Frame(self.root_frame)
        self.top_frame.pack(side=tk.TOP, expand=True, fill=tk.BOTH, anchor=tk.S, pady=2)
        self.setup_ok_cancel(True)
        self.add_new_fun = self.add_new_folder  # or can add new folder

        # move clip to folder
        # display current clip path
        cur_path, f_name = os.path.split(clip.f_name)
        filename_label = ttk.Label(self.top_frame, text='clip: {}'.format(f_name))
        filename_label.pack(side=tk.TOP)
        current_path_label = ttk.Label(self.top_frame, text='currently @ {}'.format(cur_path))
        current_path_label.pack(side=tk.TOP)

        # paginated list of all folders > subclass with checkbox/radio & keybindz
        self.mc_frame = ttk.Frame(self.top_frame, relief='ridge')
        self.mc_frame.pack(side=tk.TOP, expand=True, fill=tk.BOTH)
        instruction_text = ttk.Label(self.mc_frame, text='select a folder to move to')
        instruction_text.pack(side=tk.TOP, pady=2)

        selection_texts = [fn[0] for fn in self.parent.all_folder_names]
        self.mc_pane = MultiChoicePane(self, selection_texts, True)

    def add_new_folder(self, *args):
        ask_fun = tkfd.askdirectory
        new_folder_path = ask_fun(parent=self.root_frame, title='add folder', mustexist=True)
        if new_folder_path:
            new_folder_path = os.sep.join(new_folder_path.split('/'))
            self.parent.add_a_folder_name(new_folder_path)
            selection_texts = [fn[0] for fn in self.parent.all_folder_names]
            self.mc_pane.reset(selection_texts)

    def ok(self, *args):
        current_i = self.mc_pane.sub_frame_i
        ct = self.mc_pane.current_tab
        if ct is None:
            return
        add_i = ct.chosen_value.get()
        actual_i = current_i * self.mc_pane.NUM_OPT_PER_PAGE + add_i
        new_path = self.parent.all_folder_names[actual_i][1]
        new_fname = os.path.join(new_path, os.path.split(self.clip.f_name)[1])
        self.callback(new_fname)
        self.close()

    def close(self, *args):
        super(MoveWin, self).close() class TagWin(ChildWin):
    def __init__(self, parent, clip, callback):
        super(TagWin, self).__init__(parent, 'edit tags', 0.5, 0.5)
        self.parent = parent
        self.clip = clip
        self.callback = callback  # callback will update tags of the clip

        self.invalid_chars = set(string.punctuation.replace("_", ""))

        self.top_frame = ttk.Frame(self.root_frame)
        self.top_frame.pack(side=tk.TOP, expand=True, fill=tk.BOTH, anchor=tk.S)
        self.setup_ok_cancel(True)
        self.add_new_fun = self.add_new_tag  # or can add new folder

        clip_label = ttk.Label(self.top_frame, text='editing tags for: {}'.format(clip.name))
        clip_label.pack(side=tk.TOP, pady=2)

        self.mc_frame = ttk.Frame(self.top_frame, relief='ridge')
        self.mc_frame.pack(side=tk.TOP, expand=True, fill=tk.BOTH)
        instruction_text = ttk.Label(self.mc_frame, text='select tags')
        instruction_text.pack(side=tk.TOP, pady=2)

        starting_options = [(tag, tag in clip.tags) for tag in self.parent.all_tags]
        self.mc_pane = MultiChoicePane(self, starting_options, False)

    def ok(self, *args):
        # construct the updated list of tags
        updated_tag_list = [(k, bool(val_var.get())) for k, val_var in self.mc_pane.opt_to_val.items()]
        self.callback(updated_tag_list)
        self.close()

    def add_new_tag(self, *args):
        new_tag = tksd.askstring("add new tag", 'pls no special characters')
        if new_tag and len(new_tag) > 0:
            if any(c in self.invalid_chars for c in new_tag):
                return
            if new_tag in self.mc_pane.opt_to_val:
                self.mc_pane.opt_to_val[new_tag].set(1)
            else:
                self.mc_pane.sub_frame_i = 0
                self.mc_pane.add_opt((new_tag, True))
                self.mc_pane.switch_tab(len(self.mc_pane.sub_frames) - 1)
                self.mc_pane.update_tab_selection()

                self.parent.add_a_tag(new_tag) class AddTagWin(TagWin):
    def __init__(self, parent, clips, callback):
        super(TagWin, self).__init__(parent, 'add tags', 0.5, 0.5)
        self.parent = parent
        self.clips = clips
        self.callback = callback  # callback will add tags to every clip

        self.invalid_chars = set(string.punctuation.replace("_", ""))

        self.top_frame = ttk.Frame(self.root_frame)
        self.top_frame.pack(side=tk.TOP, expand=True, fill=tk.BOTH, anchor=tk.S)
        self.setup_ok_cancel(True)
        self.add_new_fun = self.add_new_tag  # or can add new folder

        clip_label = ttk.Label(self.top_frame, text='adding tags for {} clips'.format(len(self.clips)))
        clip_label.pack(side=tk.TOP, pady=2)

        self.mc_frame = ttk.Frame(self.top_frame, relief='ridge')
        self.mc_frame.pack(side=tk.TOP, expand=True, fill=tk.BOTH)
        instruction_text = ttk.Label(self.mc_frame, text='select tags to add')
        instruction_text.pack(side=tk.TOP, pady=2)

        starting_options = [(tag, False) for tag in self.parent.all_tags]
        self.mc_pane = MultiChoicePane(self, starting_options, False)

    def ok(self, *args):
        # construct the updated list of tags
        updated_tag_list = [(k, True) for k, val_var in self.mc_pane.opt_to_val.items() if bool(val_var.get())]
        self.callback(updated_tag_list)
        self.close() class Treeview:
    def __init__(self, containing_frame, bind_to, select_mode='extended', enabled_cols=[0, 1, 2]):
        # select_mode can also be 'browse' if you want only 1 to be possible to select
        # enabled cols says which columns to actually display
        col_nos = ['#0', '#1', '#2']
        col_headings = ['', 'tags', 'full path']
        col_stretch = [1, 0, 0]
        col_ws = [300, 300, 400]

        self.frame = containing_frame
        self.bind_to = bind_to
        self.inner_frame = ttk.Frame(self.frame)

        self.bind_to.bind('<KeyRelease-Home>', self.go_home)
        self.bind_to.bind('<Prior>', self.page_up)
        self.last_bot_loc = 0
        self.row_offset = 35
        self.bind_to.bind('<KeyRelease-Next>', self.page_down)
        self.bind_to.bind('<KeyRelease-End>', self.go_end)

        self.tree = ttk.Treeview(self.inner_frame, selectmode=select_mode, height=20, columns=('tags', 'fpath'))
        self.tree.pack(side=tk.LEFT, anchor=tk.N, fill=tk.BOTH, expand=tk.Y)
        self.ysb = ttk.Scrollbar(self.frame, orient='vertical', command=self.tree.yview)
        self.tree.configure(yscrollcommand=self.ysb.set)
        self.ysb.pack(side=tk.RIGHT, anchor=tk.N, fill=tk.Y)
        self.inner_frame.pack(side=tk.TOP, anchor=tk.N, fill=tk.BOTH, expand=True)

        # ttk
        style = ttk.Style()
        style.layout("Treeview", [
            ('Treeview.treearea', {'sticky': 'nswe'})
        ])
        style.configure('Treeview', indent=2)

        # set up the columns
        for i in range(len(col_nos)):
            if i in enabled_cols:
                h, w = col_headings[i], col_ws[i]
            else:
                h, w, = '', 0
            self.tree.heading(col_nos[i], text=h)
            self.tree.column(col_nos[i], stretch=col_stretch[i], width=w)

    def get_selected_clip(self, event=None):
        cur_item = self.tree.selection()
        if len(cur_item) < 1:
            return None, None
        cur_item = cur_item[0]
        sel_clip = self.tree.item(cur_item)
        try:
            if sel_clip["values"][-1] != 'clip':
                return None, None
            return sel_clip["values"][1], cur_item
        except Exception as e:
            print(e)
            return None, None

    def delet_selected_clip(self, cur_item=None):
        if cur_item is None:
            cur_item = self.tree.selection()
            if len(cur_item) < 1:
                return
            cur_item = cur_item[0]
        sel_clip = self.tree.item(cur_item)
        next_item = self.tree.next(cur_item)
        if next_item == '':
            next_item = self.tree.prev(cur_item)
        self.tree.delete(cur_item)
        self.tree.selection_set(next_item)
        self.tree.focus(next_item)
        return sel_clip

    def delet_all_children(self, row_id):
        def check_if_clip(item_id):
            try:
                return self.tree.item(item_id)['values'][2] == 'clip'
            except:
                return False

        tor = []

        children = self.tree.get_children(row_id)
        children = list(children)

        while len(children) > 0:
            c = children.pop()
            if check_if_clip(c):
                tor.append(self.tree.item(c)['values'][1])
            else:
                children.extend(list(self.tree.get_children(c)))

        self.tree.delete(row_id)
        return tor

    def clear(self):
        self.tree.delete(*self.tree.get_children())

    def select_top(self, *args):
        to_select = self.tree.identify_row(self.row_offset)  # height offset from row titles
        self.tree.selection_set(to_select)
        self.tree.focus(to_select)

    def go_home(self, event=None):
        self.last_bot_loc = 0.0
        to_select = self.tree.get_children()[0]

        self.tree.selection_set(to_select)
        self.tree.focus(to_select)
        self.tree.yview_moveto(0)

    def go_end(self, event=None):
        self.last_bot_loc = 1.0
        to_select = self.tree.get_children()[-1]

        def recurse(from_select):
            children = self.tree.get_children(from_select)
            any_open = [c for c in children if self.tree.item(c, 'open')]
            if len(any_open) == 0:
                if len(children) > 0:
                    return children[-1]
                else:
                    return from_select
            else:
                return recurse(any_open[-1])

        to_select = recurse(to_select)
        self.tree.selection_set(to_select)
        self.tree.focus(to_select)
        self.tree.yview_moveto(1)

    def page_up(self, event):
        new_bot = self.ysb.get()[1]
        if new_bot == self.last_bot_loc == 1.0:
            def do_me_later():
                self.last_bot_loc = 0.99
                self.tree.yview_moveto(1)
                self.frame.after(15, self.select_top)
            self.frame.after(10, do_me_later)
        else:
            self.select_top()
            self.last_bot_loc = self.ysb.get()[1]

    def page_down(self, event):
        new_bot = self.ysb.get()[1]
        if new_bot == self.last_bot_loc == 1.0:
            self.go_end()
        else:
            self.select_top()
            self.last_bot_loc = new_bot
import tkinter as tk
import tkinter.filedialog as tkfd
import tkinter.messagebox as tkmb
import os
from tkinter import ttk
from subprocess import Popen
from sol.inputs import osc
from sol.database import database, clip
from sol.setup_gui import SetupGui
from sol.config import GlobalConfig
from .sub_wins import RenameWin, MoveWin, TagWin, AddTagWin, Treeview
            self.add_clip_gui = ImportWizardGui(tk.Toplevel(takefocus=True), self)
        else:
            self.add_clip_gui.root.lift()
            self.add_clip_gui.root.focus_force()

    def edit_config(self):
        if self.setup_gui is not None:
            self.setup_gui.root_frame.focus_force()
        else:
            self.setup_gui = SetupGui(parent=self)

    def toggle_on_top(self, *args):
        # for config options..
        pass

    ####################
    # NEATER SUBWIN FUNS

    def get_clip_and_i(self):
        clip_fname, row_id = self.tree.get_selected_clip()
        if clip_fname is not None and len(clip_fname) > 0:
            if clip_fname in self.db.clips:
                return self.db.clips[clip_fname], row_id
        return None, None

    def callback_gen(self, clip, row_id, what_kind):
        stor_clip = clip
        i = row_id

        if what_kind in ['move', 'rename']:
            def callback(new_path, new_name=None):
                hold_vals = self.tree.tree.item(i)['values']
                old_path = hold_vals[1]
                hold_vals[1] = new_path
                self.tree.tree.item(i, values=hold_vals)
                if new_name is not None:
                    self.tree.tree.item(i, text=new_name)

                def maybe_do_later():
                    if os.path.exists(old_path):
                        def rename_later():
                            try:
                                # actually rename the file?
                                os.rename(old_path, new_path)
                                # change clip's fname/name
                                self.db.move_clip(stor_clip, new_path, new_name)
                            except:
                                pass
                        self.parent.root.after(1000, rename_later)

                if self.last_selected_clip == stor_clip:
                    self.delayed_actions += [(stor_clip, maybe_do_later)]
                else:
                    maybe_do_later()

        else:  # tag
            def callback(new_tags):
                self.db.tagdb.update_clip_tags(new_tags, stor_clip)
                self.db.tagdb.refresh()

                new_tags = stor_clip.str_tags()
                hold_vals = self.tree.tree.item(i)['values']
                hold_vals[0] = new_tags

                # change row in the treeview
                self.tree.tree.item(i, values=hold_vals)

        return callback

    #################
    # ACTION PROMPTS

    def move_prompt(self, *args):
        clip, row_id = self.get_clip_and_i()
        if clip is None:
            return
        callback = self.callback_gen(clip, row_id, 'move')
        MoveWin(self, clip, callback)

    def rename_prompt(self, *args):
        clip, row_id = self.get_clip_and_i()
        if clip is None:
            return
        callback = self.callback_gen(clip, row_id, 'rename')
        RenameWin(self, clip, callback)

    def tag_prompt(self, *args):

        def gen_tag_list_callback(cliplist, itemlist):
            clipz = cliplist
            iz = itemlist

            def gend_fun(tag_list):
                for i, clp in enumerate(clipz):
                    self.db.tagdb.update_clip_tags(tag_list, clp)
                    new_tags = clp.str_tags()
                    hold_vals = self.tree.tree.item(iz[i])['values']
                    hold_vals[0] = new_tags
                    self.tree.tree.item(iz[i], values=hold_vals)
                self.db.tagdb.refresh()

            return gend_fun

        selected_rows = self.tree.tree.selection()
        selected_ids = []
        selected_clips = []
        for r in selected_rows:
            selected_item = self.tree.tree.item(r)['values']
            if selected_item[2] == 'clip':
                clip_fname = selected_item[1]
                if (len(clip_fname) > 0) and selected_item[1] in self.db.clips:
                    selected_clips.append(self.db.clips[clip_fname])
                    selected_ids.append(r)

        if len(selected_ids) == 0:
            return
        elif len(selected_ids) == 1:
            callback = self.callback_gen(selected_clips[0], selected_ids[0], 'tag')
            TagWin(self, selected_clips[0], callback)
        else:

            AddTagWin(self, selected_clips, gen_tag_list_callback(selected_clips, selected_ids))