class BaseListItem(ThemableBehavior, RectangularRippleBehavior, ButtonBehavior, FloatLayout): """ Base class to all ListItems. Not supposed to be instantiated on its own. """ text = StringProperty() """ Text shown in the first line. :attr:`text` is a :class:`~kivy.properties.StringProperty` and defaults to `''`. """ text_color = ColorProperty(None) """ Text color in ``rgba`` format used if :attr:`~theme_text_color` is set to `'Custom'`. :attr:`text_color` is a :class:`~kivy.properties.ColorProperty` and defaults to `None`. """ font_style = StringProperty("Subtitle1") """ Text font style. See ``kivymd.font_definitions.py``. :attr:`font_style` is a :class:`~kivy.properties.StringProperty` and defaults to `'Subtitle1'`. """ theme_text_color = StringProperty("Primary", allownone=True) """ Theme text color in ``rgba`` format for primary text. :attr:`theme_text_color` is a :class:`~kivy.properties.StringProperty` and defaults to `'Primary'`. """ secondary_text = StringProperty() """ Text shown in the second line. :attr:`secondary_text` is a :class:`~kivy.properties.StringProperty` and defaults to `''`. """ tertiary_text = StringProperty() """ The text is displayed on the third line. :attr:`tertiary_text` is a :class:`~kivy.properties.StringProperty` and defaults to `''`. """ secondary_text_color = ColorProperty(None) """ Text color in ``rgba`` format used for secondary text if :attr:`~secondary_theme_text_color` is set to `'Custom'`. :attr:`secondary_text_color` is a :class:`~kivy.properties.ColorProperty` and defaults to `None`. """ tertiary_text_color = ColorProperty(None) """ Text color in ``rgba`` format used for tertiary text if :attr:`~tertiary_theme_text_color` is set to 'Custom'. :attr:`tertiary_text_color` is a :class:`~kivy.properties.ColorProperty` and defaults to `None`. """ secondary_theme_text_color = StringProperty("Secondary", allownone=True) """ Theme text color for secondary text. :attr:`secondary_theme_text_color` is a :class:`~kivy.properties.StringProperty` and defaults to `'Secondary'`. """ tertiary_theme_text_color = StringProperty("Secondary", allownone=True) """ Theme text color for tertiary text. :attr:`tertiary_theme_text_color` is a :class:`~kivy.properties.StringProperty` and defaults to `'Secondary'`. """ secondary_font_style = StringProperty("Body1") """ Font style for secondary line. See ``kivymd.font_definitions.py``. :attr:`secondary_font_style` is a :class:`~kivy.properties.StringProperty` and defaults to `'Body1'`. """ tertiary_font_style = StringProperty("Body1") """ Font style for tertiary line. See ``kivymd.font_definitions.py``. :attr:`tertiary_font_style` is a :class:`~kivy.properties.StringProperty` and defaults to `'Body1'`. """ divider = OptionProperty("Full", options=["Full", "Inset", None], allownone=True) """ Divider mode. Available options are: `'Full'`, `'Inset'` and default to `'Full'`. :attr:`divider` is a :class:`~kivy.properties.OptionProperty` and defaults to `'Full'`. """ bg_color = ColorProperty(None) """ Background color for menu item. :attr:`bg_color` is a :class:`~kivy.properties.ColorProperty` and defaults to `None`. """ _txt_left_pad = NumericProperty("16dp") _txt_top_pad = NumericProperty() _txt_bot_pad = NumericProperty() _txt_right_pad = NumericProperty(m_res.HORIZ_MARGINS) _num_lines = 3 _no_ripple_effect = BooleanProperty(False)
class COGame(Widget): center = ObjectProperty(None) target = ObjectProperty(None) nudge_x = 0. # nudge_y = -2. # Time to wait after starting the video before getting to the center target display. pre_start_vid_ts = 0.1 ITI_mean = 1. ITI_std = .2 center_target_rad = 1.5 periph_target_rad = 1.5 # SET THE POSITION OF THE EXIT BUTTONS AND THE PHOTODIODE INDICATOR LIGHT # positions are in CM measured from the center of the screen # if platform == 'darwin': exit_pos_x = (fixed_window_size_cm[0]/2)-1.5 exit_pos_y = (fixed_window_size_cm[1]/2)-1.5 exit_pos = np.array([exit_pos_x, exit_pos_y]) ind_pos_x = (fixed_window_size_cm[0]/2)-0.5 ind_pos_y = (fixed_window_size_cm[1]/2)-0.5 indicator_pos = np.array([ind_pos_x, ind_pos_y]) # elif platform == 'win32': # exit_pos = np.array([7, 4]) # indicator_pos = np.array([8, 5]) exit_rad = 1. exit_hold = 2 #seconds # SET THE HOLD AND TIMEOUT TIMES ch_timeout = 10. # ch timeout cht = .001 # center hold time target_timeout_time = 5. tht = .001 cursor = {} cursor_start = {} cursor_ids = [] anytouch_prev = False touch_error_timeout = 0. timeout_error_timeout = 0. hold_error_timeout = 0. drag_error_timeout = 0. ntargets = 4. target_distance = 4. touch = False center_target = ObjectProperty(None) periph_target1 = ObjectProperty(None) periph_target2 = ObjectProperty(None) done_init = False prev_exit_ts = np.array([0,0]) # Number of trials: trial_counter = NumericProperty(0) #indicator_txt = StringProperty('o') #indicator_txt_color = ListProperty([.5, .5, .5, 1.]) t0 = time.time() cht_text = StringProperty('') tht_text = StringProperty('') generatorz_text = StringProperty('') targ_size_text = StringProperty('') big_rew_text = StringProperty('') cht_param = StringProperty('') tht_param = StringProperty('') targ_size_param = StringProperty('') big_rew_time_param = StringProperty('') generatorz_param = StringProperty('') nudge_text = StringProperty('') nudge_param = StringProperty('') def on_touch_down(self, touch): #handle many touchs: ud = touch.ud # Add new touch to ids: self.cursor_ids.append(touch.uid) # Add cursor curs = pix2cm(np.array([touch.x, touch.y])) self.cursor[touch.uid] = curs.copy() self.cursor_start[touch.uid] = curs.copy() # set self.touch to True self.touch = True def on_touch_move(self, touch): self.cursor[touch.uid] = pix2cm(np.array([touch.x, touch.y])) self.touch = True def on_touch_up(self, touch): try: self.cursor_ids.remove(touch.uid) _ = self.cursor.pop(touch.uid) except: print('removing touch from pre-game screen') def init(self, animal_names_dict=None, rew_in=None, task_in=None, test=None, hold=None, targ_structure=None, autoquit=None, rew_var=None, targ_timeout = None, nudge_x=None, screen_top=None): import os path = os.getcwd() if 'BasalGangulia' in path: self.in_cage = True else: self.in_cage = False self.rew_cnt = 0 self.small_rew_cnt = 0 self.use_cap_sensor = False if self.use_cap_sensor: self.serial_port_cap = serial.Serial(port='COM5') self.rhtouch_sensor = 0. targ_timeout_opts = [15, 30, 45, 60] for i, val in enumerate(targ_timeout['tt']): if val: self.target_timeout_time = targ_timeout_opts[i] small_rew_opts = [0., .1, .3, .5] for i, val in enumerate(rew_in['small_rew']): if val: small_rew = small_rew_opts[i] big_rew_opts = [0., .3, .5, .7] for i, val in enumerate(rew_in['big_rew']): if val: big_rew = big_rew_opts[i] if rew_in['rew_anytouch']: self.reward_for_anytouch = [True, small_rew] else: self.reward_for_anytouch = [False, 0] if np.logical_or(rew_in['rew_targ'], rew_in['rew_center_pls_targ']): self.reward_for_targtouch = [True, big_rew] else: self.reward_for_targtouch = [False, 0] if rew_in['rew_center_pls_targ']: self.reward_for_center = [True, small_rew] else: self.reward_for_center = [False, 0] if rew_in['snd_only']: self.reward_for_targtouch = [True, 0.] self.skip_juice = True else: self.skip_juice = False # NUDGE X nudge_x_opts = [-6, -4, -2, 0, 2, 4, 6] for i, val in enumerate(nudge_x['nudge_x']): if val: self.nudge_x = nudge_x_opts[i] # WHERE TO CONSIDER THE TOP OF THE SCREEN (HOW MUCH TO SHRINK IT DOWN BY) screen_top_opts = [-12, -10, -8, -6, -4, -2, 0] for i, val in enumerate(screen_top['screen_top']): if val: self.screen_top = screen_top_opts[i] self.center_target_position = np.array([0., 0.]) if self.in_cage: self.center_target_position[0] = self.center_target_position[0] - 4 else: self.center_target_position[0] = self.center_target_position[0] + self.nudge_x # lower the center target by half of the total amount the screen height has been shrunk by self.center_target_position[1] = self.center_target_position[1] + self.screen_top/2 # TARGET RADIUS target_rad_opts = [.5, .75, .82, .91, 1.0, 1.5, 2.25, 3.0] for i, val in enumerate(task_in['targ_rad']): if val: self.periph_target_rad = target_rad_opts[i] self.center_target_rad = target_rad_opts[i] # TARGET 1 POSITION target1_pos_opts = ['upper_left', 'lower_left', 'upper_right', 'lower_right'] for i, val in enumerate(task_in['targ1_pos']): if val: self.periph_target_pos_str = target1_pos_opts[i] d_center2top = (fixed_window_size_cm[1]/2)+(self.screen_top/2) max_y_from_center = d_center2top-self.periph_target_rad if self.periph_target_pos_str == 'upper_right': # angle = 0.25*np.pi targ1_x = max_y_from_center+self.nudge_x targ1_y = self.center_target_position[1] + max_y_from_center elif self.periph_target_pos_str == 'lower_right': # angle = 1.75*np.pi targ1_x = max_y_from_center+self.nudge_x targ1_y = self.center_target_position[1] - max_y_from_center elif self.periph_target_pos_str == 'lower_left': # angle = 1.25*np.pi targ1_x = -max_y_from_center+self.nudge_x targ1_y = self.center_target_position[1] - max_y_from_center elif self.periph_target_pos_str == 'upper_left': # angle = 0.75*np.pi targ1_x = -max_y_from_center+self.nudge_x targ1_y = self.center_target_position[1] + max_y_from_center # target_distance = 6. # distance from center of screen to target # self.target1_position = np.array([np.cos(angle)*target_distance+self.nudge_x, np.sin(angle)*target_distance+self.nudge_y]) self.target1_position = np.array([targ1_x, targ1_y]) self.periph_target_position = self.target1_position self.target_index = 1 # TARGET 2 POSITION target2_pos_opts = ['left', 'right', 'above', 'below'] for i, val in enumerate(task_in['targ2_pos']): if val: self.target2_pos_str = target2_pos_opts[i] targ1_to_targ2_dist_opts = [0, 3, 6, 9, 12, 15] for i, val in enumerate(task_in['targ1_to_targ2_dist']): if val: self.targ1_to_targ2_dist = targ1_to_targ2_dist_opts[i] # self.target2_position = np.array([np.cos(angle)*target_distance+self.nudge_x, np.sin(angle)*target_distance+self.nudge_y]) self.target2_position = np.array([targ1_x, targ1_y]) if self.target2_pos_str == 'left': self.target2_position[0] = self.target2_position[0]-2*self.periph_target_rad-self.targ1_to_targ2_dist elif self.target2_pos_str == 'right': self.target2_position[0] = self.target2_position[0]+2*self.periph_target_rad+self.targ1_to_targ2_dist elif self.target2_pos_str == 'above': self.target2_position[1] = self.target2_position[1]+2*self.periph_target_rad+self.targ1_to_targ2_dist elif self.target2_pos_str == 'below': self.target2_position[1] = self.target2_position[1]-2*self.periph_target_rad-self.targ1_to_targ2_dist # HOW MUCH TIME TO WAIT UNTIL THE NEXT TARGET APPEARS self.time_to_next_targ = False # ANIMAL NAME for i, (nm, val) in enumerate(animal_names_dict.items()): if val: animal_name = nm self.use_center = False for i, (nm, val) in enumerate(targ_structure.items()): if val: generatorz = getattr(self, nm) self.generatorz_param2 = nm if 'co' in nm: self.use_center = True holdz = [0.0, 0.1, 0.2, 0.3, 0.4, .5, .6, '.4-.6'] self.cht_type = None self.tht_type = None for i, val in enumerate(hold['hold']): if val: if type(holdz[i]) is str: mx, mn = holdz[i].split('-') self.tht_type = holdz[i] self.tht = (float(mn)+float(mx))*.5 else: self.tht = holdz[i] for i, val in enumerate(hold['chold']): if val: if type(holdz[i]) is str: mx, mn = holdz[i].split('-') self.cht_type = holdz[i] self.cht = (float(mn)+float(mx))*.5 else: self.cht = holdz[i] try: pygame.mixer.init() except: pass # reward_delay_opts = [0., .4, .8, 1.2] # for i, val in enumerate(rew_del['rew_del']): # if val: self.reward_delay_time = 0.0 reward_var_opt = [1.0, .5, .33] for i, val in enumerate(rew_var['rew_var']): if val: self.percent_of_trials_rewarded = reward_var_opt[i] if self.percent_of_trials_rewarded == 0.33: self.percent_of_trials_doubled = 0.1 else: self.percent_of_trials_doubled = 0.0 self.reward_generator = self.gen_rewards(self.percent_of_trials_rewarded, self.percent_of_trials_doubled, self.reward_for_targtouch) # white_screen_opts = [True, False] # for i, val in enumerate(white_screen['white_screen']): # if val: self.use_white_screen = False test_vals = [True, False, False] in_cage_vals = [False, False, True] for i, val in enumerate(test['test']): if val: self.testing = test_vals[i] #self.in_cage = in_cage_vals[i] autoquit_trls = [10, 25, 50, 100, 10**10] for i, val in enumerate(autoquit['autoquit']): if val: self.max_trials = autoquit_trls[i] # drag_ok = [True, False] # for i, val in enumerate(drag['drag']): # if val: # self.drag_ok = drag_ok[i] self.drag_ok = False; # nudge_9am_dist = [0., .5, 1.] # for i, val in enumerate(nudge['nudge']): # if val: self.nudge_dist = 0. # targ_pos = ['corners', None] # for i, val in enumerate(targ_pos['targ_pos']): # if val: self.generator_kwarg = 'corners' # Preload sounds: self.reward1 = SoundLoader.load('reward1.wav') self.reward2 = SoundLoader.load('reward2.wav') self.state = 'ITI' self.state_start = time.time() self.ITI = self.ITI_std + self.ITI_mean # Initialize targets: self.center_target.set_size(2*self.center_target_rad) self.center_target.move(self.center_target_position) self.periph_target1.set_size(2*self.periph_target_rad) self.periph_target2.set_size(2*self.periph_target_rad) self.exit_target1.set_size(2*self.exit_rad) self.exit_target2.set_size(2*self.exit_rad) self.indicator_targ.set_size(self.exit_rad) self.indicator_targ.move(self.indicator_pos) self.indicator_targ.color = (0., 0., 0., 1.) self.exit_target1.move(self.exit_pos) self.exit_pos2 = np.array([self.exit_pos[0], -1*self.exit_pos[1]]) self.exit_target2.move(self.exit_pos2) self.exit_target1.color = (.15, .15, .15, 1) self.exit_target2.color = (.15, .15, .15, 1) self.repeat = False self.FSM = dict() self.FSM['ITI'] = dict(end_ITI='vid_trig', stop=None) self.FSM['vid_trig'] = dict(rhtouch='target', stop=None) if self.use_center: self.FSM['vid_trig'] = dict(end_vid_trig='center', stop=None) self.FSM['center'] = dict(touch_center='center_hold', center_timeout='timeout_error', non_rhtouch='RH_touch',stop=None) self.FSM['center_hold'] = dict(finish_center_hold='target', early_leave_center_hold='hold_error', non_rhtouch='RH_touch', stop=None) self.FSM['target'] = dict(touch_target = 'targ_hold', target_timeout='timeout_error', stop=None, anytouch='rew_anytouch', non_rhtouch='RH_touch')#,touch_not_target='touch_error') self.FSM['targ_hold'] = dict(finish_targ2_hold='reward', finish_targ1_hold='target', early_leave_target_hold = 'hold_error', targ_drag_out = 'drag_error', stop=None, non_rhtouch='RH_touch') self.FSM['reward'] = dict(end_reward = 'ITI', stop=None, non_rhtouch='RH_touch') if self.use_center: return_ = 'center' else: return_ = 'target' self.FSM['touch_error'] = dict(end_touch_error=return_, stop=None, non_rhtouch='RH_touch') self.FSM['timeout_error'] = dict(end_timeout_error='ITI', stop=None, non_rhtouch='RH_touch') self.FSM['hold_error'] = dict(end_hold_error=return_, stop=None, non_rhtouch='RH_touch') self.FSM['drag_error'] = dict(end_drag_error=return_, stop=None, non_rhtouch='RH_touch') self.FSM['rew_anytouch'] = dict(end_rewanytouch='target', stop=None, non_rhtouch='RH_touch') self.FSM['idle_exit'] = dict(stop=None) try: self.reward_port = serial.Serial(port='COM4', baudrate=115200) self.reward_port.close() except: pass try: self.dio_port = serial.Serial(port='COM5', baudrate=115200) time.sleep(4.) except: pass try: self.cam_trig_port = serial.Serial(port='COM6', baudrate=9600) time.sleep(3.) # Say hello: self.cam_trig_port.write('a'.encode()) # Start cams @ 50 Hz self.cam_trig_port.write('1'.encode()) except: pass # save parameters: d = dict(animal_name=animal_name, center_target_rad=self.center_target_rad, periph_target_rad=self.periph_target_rad, target_structure = generatorz.__name__, target1_position = self.target1_position, target2_position = self.target2_position, ITI_mean=self.ITI_mean, ITI_std = self.ITI_std, ch_timeout=self.ch_timeout, cht=self.cht, reward_time_small=self.reward_for_center[1], reward_time_big=self.reward_for_targtouch[1], reward_for_anytouch=self.reward_for_anytouch[0], reward_for_center = self.reward_for_center[0], reward_for_targtouch=self.reward_for_targtouch[0], touch_error_timeout = self.touch_error_timeout, timeout_error_timeout = self.timeout_error_timeout, hold_error_timeout = self.hold_error_timeout, drag_error_timeout = self.drag_error_timeout, ntargets = self.ntargets, target_distance = self.target_distance, start_time = datetime.datetime.now().strftime('%Y%m%d_%H%M'), testing=self.testing, rew_delay = self.reward_delay_time, use_cap_sensor = self.use_cap_sensor, drag_ok = self.drag_ok, ) print(self.reward_for_center) print(self.reward_for_targtouch) print(self.reward_for_anytouch) #try: if self.testing or platform == 'darwin': pass else: import os path = os.getcwd() path = path.split('\\') path_data = [p for p in path if np.logical_and('Touch' not in p, 'Targ' not in p)] path_root = '' for ip in path_data: path_root += ip+'/' p = path_root + 'data/' print('Auto path : %s'%p) # Check if this directory exists: if os.path.exists(p): pass else: p = path_root+ 'data_tmp_'+datetime.datetime.now().strftime('%Y%m%d')+'/' if os.path.exists(p): pass else: os.mkdir(p) print('Making temp directory: ', p) print ('') print ('') print('Data saving PATH: ', p) print ('') print ('') self.filename = p+ animal_name+'_'+datetime.datetime.now().strftime('%Y%m%d_%H%M') if self.in_cage: self.filename = self.filename+'_cage' pickle.dump(d, open(self.filename+'_params.pkl', 'wb')) self.h5file = tables.open_file(self.filename + '_data.hdf', mode='w', title = 'NHP data') self.h5_table = self.h5file.create_table('/', 'task', Data, '') self.h5_table_row = self.h5_table.row self.h5_table_row_cnt = 0 # Note in python 3 to open pkl files: #with open('xxxx_params.pkl', 'rb') as f: # data_params = pickle.load(f) # except: # pass def gen_rewards(self, perc_trials_rew, perc_trials_2x, reward_for_grasp): mini_block = int(2*(np.round(1./self.percent_of_trials_rewarded))) rew = [] trial_cnt_bonus = 0 for i in range(500): mini_block_array = np.zeros((mini_block)) ix = np.random.permutation(mini_block) mini_block_array[ix[:2]] = reward_for_grasp[1] trial_cnt_bonus += mini_block if perc_trials_2x > 0: if trial_cnt_bonus > int(1./(perc_trials_rew*perc_trials_2x)): mini_block_array[ix[0]] = reward_for_grasp[1]*2. trial_cnt_bonus = 0 rew.append(mini_block_array) return np.hstack((rew)) def close_app(self): # Save Data Eventually #Stop the video: try: self.cam_trig_port.write('0'.encode()) except: pass if self.use_cap_sensor: self.serial_port_cap.close() if self.idle: self.state = 'idle_exit' self.trial_counter = -1 # Set relevant params text: self.cht_text = 'Center Hold Time: ' self.tht_text = 'Target Hold Time: ' self.generatorz_text = 'Target Structure: ' self.targ_size_text = 'Target Radius: ' self.big_rew_text = 'Big Reward Time: ' if type(self.cht_type) is str: self.cht_param = self.cht_type else: self.cht_param = 'Constant: ' + str(self.cht) if type(self.tht_type) is str: self.tht_param = self.tht_type else: self.tht_param = 'Constant: ' + str(self.tht) self.targ_size_param = str(self.center_target_rad) self.big_rew_time_param = str(self.reward_for_targtouch[1]) self.generatorz_param = self.generatorz_param2 self.nudge_text = 'Nudge 9oclock targ? ' self.nudge_param = str(self.nudge_dist) else: App.get_running_app().stop() Window.close() def update(self, ts): self.state_length = time.time() - self.state_start self.rew_cnt += 1 self.small_rew_cnt += 1 # Run task update functions: for f, (fcn_test_name, next_state) in enumerate(self.FSM[self.state].items()): kw = dict(ts=self.state_length) fcn_test = getattr(self, fcn_test_name) if fcn_test(**kw): # if stop: close the app if fcn_test_name == 'stop': self.close_app() else: # Run any 'end' fcns from prevoius state: end_state_fn_name = "_end_%s" % self.state if hasattr(self, end_state_fn_name): end_state_fn = getattr(self, end_state_fn_name) end_state_fn() self.prev_state = self.state self.state = next_state self.state_start = time.time() # Run any starting functions: start_state_fn_name = "_start_%s" % self.state if hasattr(self, start_state_fn_name): start_state_fn = getattr(self, start_state_fn_name) start_state_fn() break else: while_state_fn_name = "_while_%s" % self.state if hasattr(self, while_state_fn_name): while_state_fn = getattr(self, while_state_fn_name) while_state_fn() if self.use_cap_sensor: try: self.serial_port_cap.flushInput() port_read = self.serial_port_cap.read(4) if str(port_read[:2]) == "b'N1'": self.rhtouch_sensor = False elif str(port_read[:2]) == "b'C1'": self.rhtouch_sensor = True print(self.rhtouch_sensor) except: print('passing state! ') pass if self.testing or platform == 'darwin': pass else: if self.state == 'idle_exit': pass else: self.write_to_h5file() def write_to_h5file(self): self.h5_table_row['state']= self.state; cursor = np.zeros((10, 2)) cursor[:] = np.nan for ic, curs_id in enumerate(self.cursor_ids): cursor[ic, :] = self.cursor[curs_id] self.h5_table_row['cursor'] = cursor cursor_id = np.zeros((10, )) cursor_id[:] = np.nan cursor_id[:len(self.cursor_ids)] = self.cursor_ids self.h5_table_row['cursor_ids'] = cursor_id self.h5_table_row['target_pos'] = self.periph_target_position self.h5_table_row['time'] = time.time() - self.t0 self.h5_table_row['cap_touch'] = self.rhtouch_sensor self.h5_table_row.append() # Write DIO try: self.write_row_to_dio() except: pass # Upgrade table row: self.h5_table_row_cnt += 1 def write_row_to_dio(self): ### FROM TDT TABLE, 5 is GND, BYTE A ### row_to_write = self.h5_table_row_cnt % 256 ### write to arduino: word_str = b'd' + struct.pack('<H', int(row_to_write)) self.dio_port.write(word_str) def stop(self, **kwargs): # If past number of max trials then auto-quit: if np.logical_and(self.trial_counter >= self.max_trials, self.state == 'ITI'): self.idle = True return True else: e = [0, 0] e[0] = self.check_if_cursors_in_targ(self.exit_pos, self.exit_rad) e[1] = self.check_if_cursors_in_targ(self.exit_pos2, self.exit_rad) t = [0, 0] for i in range(2): if np.logical_and(self.prev_exit_ts[i] !=0, e[i] == True): t[i] = time.time() - self.prev_exit_ts[i] elif np.logical_and(self.prev_exit_ts[i] == 0, e[i]==True): self.prev_exit_ts[i] = time.time() else: self.prev_exit_ts[i] = 0 if t[0] > self.exit_hold and t[1] > self.exit_hold: self.idle = False return True else: return False def _start_ITI(self, **kwargs): try: self.cam_trig_port.write('0'.encode()) except: pass Window.clearcolor = (0., 0., 0., 1.) self.exit_target1.color = (.15, .15, .15, 1.) self.exit_target2.color = (.15, .15, .15, 1.) # Set ITI, CHT, THT self.ITI = np.random.random()*self.ITI_std + self.ITI_mean if type(self.cht_type) is str: cht_min, cht_max = self.cht_type.split('-') self.cht = ((float(cht_max) - float(cht_min)) * np.random.random()) + float(cht_min) if type(self.tht_type) is str: tht_min, tht_max = self.tht_type.split('-') self.tht = ((float(tht_max) - float(tht_min)) * np.random.random()) + float(tht_min) self.center_target.color = (0., 0., 0., 0.) self.periph_target1.color = (0., 0., 0., 0.) self.periph_target2.color = (0., 0., 0., 0.) self.indicator_targ.color = (0., 0., 0., 0.) def end_ITI(self, **kwargs): return kwargs['ts'] > self.ITI def _start_vid_trig(self, **kwargs): if self.trial_counter == 0: time.sleep(1.) try: self.cam_trig_port.write('1'.encode()) except: pass self.first_target_attempt = True self.first_time_for_this_targ = True if np.logical_and(self.use_cap_sensor, not self.rhtouch_sensor): self.periph_target1.color = (1., 0., 0., 1.) self.periph_target2.color = (1., 0., 0., 1.) self.center_target.color = (1., 0., 0., 1.) Window.clearcolor = (1., 0., 0., 1.) # Turn exit buttons redish: self.exit_target1.color = (.9, 0, 0, 1.) self.exit_target2.color = (.9, 0, 0, 1.) def end_vid_trig(self, **kwargs): return kwargs['ts'] > self.pre_start_vid_ts def rhtouch(self, **kwargs): if self.use_cap_sensor: if self.rhtouch_sensor: return True else: return False else: return True def non_rhtouch(self, **kwargs): x = not self.rhtouch() # if x: # self.repeat = True return x def _start_center(self, **kwargs): Window.clearcolor = (0., 0., 0., 1.) self.center_target.color = (1., 1., 0., 1.) self.exit_target1.color = (.15, .15, .15, 1) self.exit_target2.color = (.15, .15, .15, 1) self.periph_target1.color = (0., 0., 0., 0.) ### Make peripheral target alpha = 0 so doesn't obscure self.indicator_targ.color = (.25, .25, .25, 1.) # Reset target index back to 1 self.target_index = 1 if self.first_time_for_this_targ: self.first_time_for_this_targ_t0 = time.time() self.periph_target2.color = (0., 0., 0., 0.) self.first_time_for_this_targ = False def _while_center(self, **kwargs): # check and see if it is time for the next target to appear if self.time_to_next_targ is not False: # import pdb; pdb.set_trace() if time.time() - self.first_time_for_this_targ_t0 > self.time_to_next_targ: # illuminate the next target self.periph_target2.move(self.target1_position) self.periph_target2.color = (1., 1., 0., 1.) def _start_center_hold(self, **kwargs): self.center_target.color = (0., 1., 0., 1.) self.indicator_targ.color = (0.75, .75, .75, 1.) def _start_targ_hold(self, **kwargs): self.periph_target1.color = (0., 1., 0., 1.) self.indicator_targ.color = (0.75, .75, .75, 1.) def _end_center_hold(self, **kwargs): self.center_target.color = (0., 0., 0., 1.) self.first_time_for_this_targ = True def _end_target_hold(self, **kwargs): self.periph_target1.color = (0., 0., 0., 0.) self.first_time_for_this_targ = True def _start_touch_error(self, **kwargs): self.center_target.color = (0., 0., 0., 1.) self.periph_target1.color = (0., 0., 0., 1.) self.periph_target2.color = (0., 0., 0., 1.) self.repeat = True def _start_timeout_error(self, **kwargs): self.center_target.color = (0., 0., 0., 1.) self.periph_target1.color = (0., 0., 0., 1.) self.periph_target2.color = (0., 0., 0., 1.) #self.repeat = True def _start_hold_error(self, **kwargs): self.center_target.color = (0., 0., 0., 1.) self.periph_target1.color = (0., 0., 0., 1.) self.periph_target2.color = (0., 0., 0., 1.) self.repeat = True def _start_drag_error(self, **kwargs): self.center_target.color = (0., 0., 0., 1.) self.periph_target1.color = (0., 0., 0., 1.) self.periph_target2.color = (0., 0., 0., 1.) self.repeat = True def _start_target(self, **kwargs): Window.clearcolor = (0., 0., 0., 1.) self.center_target.color = (0., 0., 0., 0.) if self.target_index == 1: self.periph_target_position = self.target1_position elif self.target_index == 2: self.periph_target_position = self.target2_position self.periph_target1.move(self.periph_target_position) self.periph_target1.color = (1., 1., 0., 1.) self.repeat = False self.exit_target1.color = (.15, .15, .15, 1) self.exit_target2.color = (.15, .15, .15, 1) self.indicator_targ.color = (.25, .25, .25, 1.) if self.first_target_attempt: self.first_target_attempt_t0 = time.time(); self.first_target_attempt = False if self.first_time_for_this_targ: self.first_time_for_this_targ_t0 = time.time() self.periph_target2.color = (0., 0., 0., 0.) self.first_time_for_this_targ = False def _while_target(self, **kwargs): # check and see if it is time for the next target to appear if self.time_to_next_targ is not False: # import pdb; pdb.set_trace() if time.time() - self.first_time_for_this_targ_t0 > self.time_to_next_targ and self.target_index == 1: # illuminate the next target self.periph_target2.move(self.target2_position) self.periph_target2.color = (1., 1., 0., 1.) def _start_reward(self, **kwargs): self.trial_counter += 1 Window.clearcolor = (1., 1., 1., 1.) self.periph_target1.color = (1., 1., 1., 1.) self.periph_target2.color = (1., 1., 1., 1.) self.exit_target1.color = (1., 1., 1., 1.) self.exit_target2.color = (1., 1., 1., 1.) self.rew_cnt = 0 self.cnts_in_rew = 0 self.indicator_targ.color = (1., 1., 1., 1.) self.repeat = False def _while_reward(self, **kwargs): if self.rew_cnt == 1: self.run_big_rew() self.rew_cnt += 1 def _start_rew_anytouch(self, **kwargs): #if self.small_rew_cnt == 1: if self.reward_for_anytouch[0]: self.run_small_rew() else: self.repeat = True #self.small_rew_cnt += 1 def run_big_rew(self, **kwargs): try: print('in big reward:') self.repeat = False if self.reward_for_targtouch[0]: #winsound.PlaySound('beep1.wav', winsound.SND_ASYNC) #sound = SoundLoader.load('reward1.wav') print('in big reward 2') #print(str(self.reward_generator[self.trial_counter])) #print(self.trial_counter) #print(self.reward_generator[:100]) self.reward1 = SoundLoader.load('reward1.wav') self.reward1.play() if not self.skip_juice: if self.reward_generator[self.trial_counter] > 0: self.reward_port.open() #rew_str = [ord(r) for r in 'inf 50 ml/min '+str(self.reward_for_targtouch[1])+' sec\n'] rew_str = [ord(r) for r in 'inf 50 ml/min '+str(self.reward_generator[self.trial_counter])+' sec\n'] self.reward_port.write(rew_str) time.sleep(.25 + self.reward_delay_time) run_str = [ord(r) for r in 'run\n'] self.reward_port.write(run_str) self.reward_port.close() except: pass def run_small_rew(self, **kwargs): try: if np.logical_or(self.reward_for_anytouch[0], self.reward_for_center[0]): #winsound.PlaySound('beep1.wav', winsound.SND_ASYNC) sound = SoundLoader.load('reward2.wav') sound.play() ### To trigger reward make sure reward is > 0: if np.logical_or(np.logical_and(self.reward_for_anytouch[0], self.reward_for_anytouch[1] > 0), np.logical_and(self.reward_for_center[0], self.reward_for_center[1] > 0)): self.reward_port.open() if self.reward_for_anytouch[0]: rew_str = [ord(r) for r in 'inf 50 ml/min '+str(self.reward_for_anytouch[1])+' sec\n'] elif self.reward_for_center[0]: rew_str = [ord(r) for r in 'inf 50 ml/min '+str(self.reward_for_center[1])+' sec\n'] self.reward_port.write(rew_str) time.sleep(.25) run_str = [ord(r) for r in 'run\n'] self.reward_port.write(run_str) self.reward_port.close() except: pass #self.repeat = True def end_reward(self, **kwargs): self.indicator_txt_color = (1.,1., 1., 1.) if self.use_white_screen: if len(self.cursor_ids)== 0: return True else: if self.cnts_in_rew > 30: return True else: self.cnts_in_rew += 1 return False def end_rewanytouch(self, **kwargs): if self.small_rew_cnt > 1: return True else: return False def end_touch_error(self, **kwargs): return kwargs['ts'] >= self.touch_error_timeout def end_timeout_error(self, **kwargs): return kwargs['ts'] >= self.timeout_error_timeout def end_hold_error(self, **kwargs): return kwargs['ts'] >= self.hold_error_timeout def end_drag_error(self, **kwargs): return kwargs['ts'] >= self.drag_error_timeout def touch_center(self, **kwargs): if self.drag_ok: return self.check_if_cursors_in_targ(self.center_target_position, self.center_target_rad) else: return np.logical_and(self.check_if_cursors_in_targ(self.center_target_position, self.center_target_rad), self.check_if_started_in_targ(self.center_target_position, self.center_target_rad)) def center_timeout(self, **kwargs): return kwargs['ts'] > self.ch_timeout def finish_center_hold(self, **kwargs): if self.cht <= kwargs['ts']: if self.reward_for_center[0]: self.run_small_rew() return True else: return False def early_leave_center_hold(self, **kwargs): return not self.check_if_cursors_in_targ(self.center_target_position, self.center_target_rad) def center_drag_out(self, **kwargs): touch = self.touch self.touch = True stay_in = self.check_if_cursors_in_targ(self.center_target_position, self.center_target_rad) self.touch = touch return not stay_in def touch_target(self, **kwargs): if self.drag_ok: return self.check_if_cursors_in_targ(self.periph_target_position, self.periph_target_rad) else: return np.logical_and(self.check_if_cursors_in_targ(self.periph_target_position, self.periph_target_rad), self.check_if_started_in_targ(self.periph_target_position, self.periph_target_rad)) def target_timeout(self, **kwargs): #return kwargs['ts'] > self.target_timeout_time if time.time() - self.first_target_attempt_t0 > self.target_timeout_time: self.repeat = False return True def finish_targ1_hold(self, **kwargs): if self.target_index == 1: if self.tht <= kwargs['ts']: # Play a small reward tone sound = SoundLoader.load('reward2.wav') sound.play() self.target_index = 2 return True else: return False else: return False def finish_targ2_hold(self, **kwargs): if self.target_index == 2: return self.tht <= kwargs['ts'] else: return False def early_leave_target_hold(self, **kwargs): return not self.check_if_cursors_in_targ(self.periph_target_position, self.periph_target_rad) def targ_drag_out(self, **kwargs): touch = self.touch self.touch = True stay_in = self.check_if_cursors_in_targ(self.periph_target_position, self.periph_target_rad) self.touch = touch return not stay_in def anytouch(self, **kwargs): if not self.touch_target(): current_touch = len(self.cursor_ids) > 0 rew = False if current_touch and not self.anytouch_prev: rew = True self.anytouch_prev = current_touch return rew else: return False def get_4targets(self, target_distance=4, nudge=0., gen_kwarg=None): return self.get_targets_co(target_distance=target_distance, nudge=0.) def get_targets_co(self, target_distance=4, nudge=0., gen_kwarg=None, ntargets=4): # Targets in CM: if gen_kwarg == 'corners': angle = np.linspace(0, 2*np.pi, 5)[:-1] + (np.pi/4.) target_distance = 6. else: angle = np.linspace(0, 2*np.pi, ntargets+1)[:-1] if self.in_cage: offset = np.array([-4., 0.]) nudge_targ = np.array([0, 0, 0, 0]) target_distance = 3. else: offset = np.array([0., 0.]) nudge_targ = np.array([0, 0, 1., 0]) x = np.cos(angle)*target_distance y = np.sin(angle)*target_distance tmp = np.hstack((x[:, np.newaxis], y[:, np.newaxis])) ### Add offset to the target positions tmp = tmp + offset[np.newaxis, :] tgs = [] nudges = [] for blks in range(100): ix = np.random.permutation(tmp.shape[0]) tgs.append(tmp[ix, :]) nudges.append(nudge_targ[ix]) tgs = np.vstack((tgs)) nudges = np.hstack((nudges)) nudge_ix = np.nonzero(nudges==1)[0] #print('Nudges: ') #print(len(nudge_ix)) to_nudge = np.array([-1., 1.])*nudge tgs[nudge_ix, :] = tgs[nudge_ix, :] + to_nudge[np.newaxis, :] return tgs def get_targets_rand(self, target_distance=4): # Targets in CM: angle = np.linspace(0, 2*np.pi, 1000) target_distance = np.linspace(target_distance/4., target_distance, 1000) ix_ang = np.random.permutation(1000) ix_dist = np.random.permutation(1000) x = np.cos(angle[ix_ang])*target_distance[ix_dist] y = np.sin(angle[ix_ang])*target_distance[ix_dist] return np.hstack((x[:, np.newaxis], y[:, np.newaxis])) def check_if_started_in_targ(self, targ_center, targ_rad): startedInTarg = False if self.touch: for id_ in self.cursor_ids: # If in target: if np.linalg.norm(np.array(self.cursor[id_]) - targ_center) < targ_rad: if np.linalg.norm(np.array(self.cursor_start[id_]) - targ_center) < targ_rad: startedInTarg = True return startedInTarg def check_if_cursors_in_targ(self, targ_center, targ_rad): if self.touch: inTarg = False for id_ in self.cursor_ids: if np.linalg.norm(np.array(self.cursor[id_]) - targ_center) < targ_rad: inTarg = True return inTarg else: return False
class Effects(Screen): events_callback = ObjectProperty(None) sets = ObjectProperty(None) title_previous = StringProperty('') # 액션바 selectedImagePath = StringProperty('') def __init__(self, **kwargs): super(Effects, self).__init__(**kwargs) self.pos = (0, 0) self.size_hint = (1, 1) self.config = ConfigParser() # effect 효과의 타이틀명, 샘플이지미파일, 효과 프로그램명 정보 읽어오기 def _get_effects(self): self.config.read(os.path.join(directory, 'Libs/mangopaint.ini')) _items = self.config.items('Effects') for key, _item in _items: img_sh = _item.split(',') _data = { 'key': key, 'image': img_sh[0], 'pgm': img_sh[1], 'order': img_sh[2] } self.effects_data.list.append(_data) # effects.kv 에서 호출하는 effects 화면 생성하기 def create_effects_screen(self, imagePath): self.effects_data = Box() self.effects_data.pos = 0 self.effects_data.list = [] self._get_effects() self.effects_data.list.sort(key=lambda x: int(x['order'])) self.effectsbar = EffectsBar(meta=self.effects_data) self.selectedImagePath = imagePath self.ids.myimage.source = self.selectedImagePath # self.container.add_widget(self.effectsbar.ret_scrollview) self.add_widget(self.effectsbar, index=0) # effectsbar 화면 랜더링 # def update_effectsbar(self): # self.effectsbar.update() # ########################################### # effect 효과를 적용하도록 화면 변경하가 # ########################################### def transform_to_image(self, image_pos): self.effects_data.pos = image_pos _data = self.effects_data.list[image_pos] cmd = [] # if 조건이 exclusive 하지 않아 혹 조건을 빠져나갈수 있는 위험이 있으니 주의할것! # 우선 뉴럴스타일이면, 다음으로 특정스타일을 물어보는 식이라... if _data.pgm == 'neural_style.py': args, output = self.neural_style_arguments(image_pos, _data.key) elif _data.key == 'edge_detect': args, output = self.edge_detect_arguments(image_pos) elif _data.key == 'water_color': args, output = self.water_color_arguments(image_pos) elif _data.key == 'oil_color': args, output = self.oil_color_arguments(image_pos) else: args, output = self.edge_detect_arguments(image_pos) cmd.append(sys.executable or 'python3') cmd.append(args) print(cmd[0] + cmd[1]) process = Popen(cmd[0] + cmd[1], shell=True, stdout=PIPE, stderr=STDOUT) out, err = process.communicate() self.ids.myimage.source = output print('effect function end: ' + _data.key + ' <==================') self.ids.myimage.reload() # 뉴럴스타일 방식의 패턴은 아래 함수로 집중 def neural_style_arguments(self, image_pos, key): # python3 ./fast_neural_style/neural_style.py eval --content-image ../../../images/Colorful-Paint-with-Paper-Texture.jpg --model ./fast_neural_style/saved_models/mosaic.model --output-image ../Data/temp.png --cuda 0 _pgm = os.path.join(directory, 'Effects/fast_neural_style', self.effects_data.list[image_pos].pgm) output = os.path.join(directory, 'Data', 'temp.png') _cuda = 1 _model = os.path.join(directory, 'Effects/fast_neural_style/saved_models', key + '.model') args = " {} eval --content-image {} --model {} --output-image {} --cuda {}".format( _pgm, self.selectedImagePath, _model, output, _cuda) return args, output # 흑백 스케치 느낌 (아직은 칼라네요....) def edge_detect_arguments(self, image_pos): # python3 edge_detecting.py --content=$1 --output=$2 --blurred=$3 # python3 edge_detecting.py --content "../../../images/mosaic.jpg" --output "../Data/mosaic_edge_result.png" --blurred 3 _pgm = os.path.join(directory, 'Effects', self.effects_data.list[image_pos].pgm) output = os.path.join(directory, 'Data', 'temp.png') args = " {} --content {} --output {} --blurred {}".format( _pgm, self.selectedImagePath, output, 3) return args, output # 유화 느낌 def oil_color_arguments(self, image_pos): # python oil_painting.py --content=$1 --output=$2 --radius=$3 --intensity=$4 # python3 oil_coloring.py --content "../../../images/mosaic.jpg" --output "../Data/mosaic_oil_result.png" --radius 5 --intensity 20 _pgm = os.path.join(directory, 'Effects', self.effects_data.list[image_pos].pgm) output = os.path.join(directory, 'Data', 'temp.png') args = " {} --content {} --output {} --radius {} --intensity {}".format( _pgm, self.selectedImagePath, output, 5, 20) return args, output # 수채화 느낌 def water_color_arguments(self, image_pos): # python3 Water_coloring.py --content=$1 --output=$2 # python3 water_coloring.py --content "../../../images/mosaic.jpg" --output "../Data/mosaic_water_result.png" _pgm = os.path.join(directory, 'Effects', self.effects_data.list[image_pos].pgm) output = os.path.join(directory, 'Data', 'temp.png') args = " {} --content {} --output {}".format(_pgm, self.selectedImagePath, output) return args, output # effects 화면에서 다시 갤러리 화면으로 돌아갈때. def remove_effects_widgets(self): print('destroyed') self.effects_data.clear()
class ShowWidget(Widget): """Widget全体を管理するスクリプト""" #------ 共通プロパティの宣言 --------------- text = StringProperty() source = StringProperty(imagedir + 'lefton.png') color = ListProperty([1, 1, 1, 1]) fileinputimage = StringProperty(imagedir + 'nomalimage.png') filepath = StringProperty() check1 = BooleanProperty(False) check2 = BooleanProperty(False) check3 = BooleanProperty(False) check_radio = BooleanProperty(True) loadfile = ObjectProperty(None) savefile = ObjectProperty(None) text_input = ObjectProperty(None) def dismiss_popup(self): self._popup.dismiss() def show_load(self): content = LoadDialog(load=self.load, cancel=self.dismiss_popup) self._popup = Popup(title="Load file", content=content, size_hint=(0.9, 0.9)) self._popup.open() def show_save(self): content = SaveDialog(save=self.save, cancel=self.dismiss_popup) self._popup = Popup(title="Save file", content=content, size_hint=(0.9, 0.9)) self._popup.open() def load(self, path, filename): with open(os.path.join(path, filename[0])) as stream: self.text_input.text = stream.read() self.dismiss_popup() def save(self, path, filename): print(path) print(filename) savedir = os.path.dirname(path) if os.path.splitext(filename)[1] == ".png": savepath = os.path.join(savedir, filename) else: savepath = os.path.join(savedir, filename + ".png") print(savepath) graphpanel = self.ids.graph_view graphpanel.save_graph(savepath) self.dismiss_popup() #def save_graph(self): # """graphの保存""" # #filechooser = Root() # #filechooser.show_save() # content = PopupChooseFile(select=self.select, cancel=self.cancel) # self.popup = Popup(title="Select MP3", content=content) # self.popup.open() #------ ShowWidgetの初期化 --------------- def __init__(self, **kwargs): super(ShowWidget, self).__init__(**kwargs) Window.bind(on_dropfile=self._on_file_drop) #------ ボタンイベント -------------------- def buttonClickedroom(self): # 左のタブがクリックされた時 # 表示画像の変更 self.source = imagedir + 'lefton.png' print("buttonClickedroom!!!") def buttonClickedfile(self): # 右のタブがクリックされた時 # 表示画像の変更 self.source = imagedir + 'righton.png' print("buttonClickedfile!!!") def checkbox_check1(self, checkbox): # チェックボックス1が押された時 self.check1 = checkbox.active return def checkbox_check2(self, checkbox): # チェックボックス2が押された時 self.check2 = checkbox.active return def checkbox_check3(self, checkbox): # チェックボックス3が押された時 self.check3 = checkbox.active return def checkbox_check(self, checkbox, funcname): global lists_id self.check_radio = checkbox.active graphpanel = self.ids.graph_view graphpanel.checkbox_check_test(funcname) configpanel = self.ids.conpanel configpanel.make_config_panel_list(funcname) # 表示グラフ指定チェックボックスが変化した時 if len(lists_id) != 0: for index, item in lists_id.items(): print(item.active) #------ ウィンドウイベント ----------------- def _on_file_drop(self, window, file_path): # ウィンドウにドロップ&ドラッグされたファイルの名前を取得 global loadfilepath # ファイル名を格納 loadfilepath = file_path.decode() # 表示ファイル名を変更 self.filepath = os.path.basename(file_path.decode()) #------ 設定パネルの再描画 ----------------- def conpanel(self): # 選択されたグラフの種類で設定パネルの中身を変更する graphpanel = self.ids.graph_view graphpanel.load_data() configpanel = self.ids.conpanel configpanel.make_config_panel(["tagai", "hamada"])
class ShellWidget(Widget): place = StringProperty() other = ObjectProperty() default_height = NumericProperty() highest_index = None lowest_index = None def __init__(self, **kwargs): super(ShellWidget, self).__init__(**kwargs) self.size_hint_y, self.height = None, 0 self.fake_children = {} def on_parent(self, _, parent): if self.place == 'top': self.other = parent._botwidget elif self.place == 'bot': self.other = parent._topwidget self.default_height = parent.default_height self.parent.bind(default_height=self.setter('default_height')) def add_widget(self, index=None, height=0): if index is None: if self.place == 'bot': len_other = len(self.other.fake_children) len_view = len(self.parent.view.children) index = len(self.fake_children) + len_other + len_view else: index = len(self.fake_children) if not self.lowest_index: self.lowest_index = index if not self.highest_index: self.highest_index = index if not height: if index in self.parent.height_cache: height = self.parent.height_cache[index] else: height = self.default_height if index > self.highest_index: self.highest_index = index elif index <= self.lowest_index: self.lowest_index = index self.fake_children[index] = {'index': index, 'height': height} self.height += height def remove_widget(self, index=None): if not index: if self.place == 'top': index = self.highest_index else: index = self.highest_index try: self.height -= self.fake_children[index]['height'] del self.fake_children[index] except Exception as e: print('ERROR KEY', e) print('LOW', self.lowest_index, 'HIGH', self.highest_index) raise Exception('NAAV') if index == self.highest_index: for x in reversed(range(self.lowest_index - 1, self.highest_index)): if x in self.fake_children: self.highest_index = x break elif index == self.lowest_index: for x in range(self.lowest_index - 1, self.highest_index + 1): if x in self.fake_children: self.lowest_index = x break def remove_widgets_fast(self, start, stop): stop = stop + 1 for index in range(start, stop): # print index self.height -= self.fake_children[index]['height'] del self.fake_children[index] def clear_widgets(self): self.fake_children = {} self.height = 0 def swap_height(self, pos): if not self.fake_children: return ## print ('{}: swap_height: {}'.format(self.place.upper(), pos)) view_count = self.parent.get_view_count() child_count = len(self.fake_children) pos = abs(pos) height_adjust = self.height - pos remlist = [] if self.place == 'bot': view_count = -view_count view_index = -child_count iter_range = range(self.lowest_index, self.highest_index + 1) else: view_index = child_count iter_range = reversed( range(self.lowest_index, self.highest_index + 1)) height = self.height time0 = time() for i in iter_range: if i not in self.fake_children: break index, child = i, self.fake_children[i] remlist.append(index) height -= child['height'] self.other.add_widget(index=index + view_count, height=child['height']) if height <= height_adjust: break len_remlist = len(remlist) if len_remlist: time1 = time() - time0 if self.place == 'top': self.remove_widgets_fast(remlist[-1], remlist[0]) self.highest_index -= len_remlist self.parent.move_view_index(self.highest_index + 1) else: self.remove_widgets_fast(remlist[0], remlist[-1]) self.lowest_index += len_remlist self.parent.move_view_index(self.other.highest_index + 1) self.highest_index = self.lowest_index + len( self.fake_children) - 1 time2 = time() - time0 def swap_one(self): if not self.fake_children: return if self.place == 'top': rem_index, add_index = self.parent.move_view_top() else: rem_index, add_index = self.parent.move_view_bot() # print ('{}: swap_one: {} to {}'.format( # self.place.upper(), rem_index, add_index)) self.remove_widget(index=rem_index) self.other.add_widget(index=add_index)
class MoveConfirmPopup(NormalPopup): """Popup that asks to confirm a file or folder move.""" target = StringProperty() photos = ListProperty() origin = StringProperty()
class RightPanelBtn(PanelBtn): pos_hint = DictProperty({'right': 1, 'bottom': 1}) text = StringProperty('Ok') def _current_screen(self): return self.parent if hasattr(self.parent, 'manager') else None def on_press(self, *args, **kwargs): _screen = self._current_screen() menu_screen = ValidObject.menu_screen(_screen.manager.get_screen('menu')) bikes_screen = ValidObject.bikes_screen(_screen.manager.get_screen('bikes')) maps_screen = ValidObject.maps_screen(_screen.manager.get_screen('maps')) shop_screen = ValidObject.shop_screen(_screen.manager.get_screen('shop')) screens = [menu_screen, bikes_screen, maps_screen, shop_screen] if 'BikesScreen' == _screen.__class__.__name__: if not app_config('bike', 'title'): bike_model = get_bike_by_title(bikes_screen.ids['title'].text) rest_rm = calc_rest_rm(bike_model.price) if Bike.buy(bike_model): RightPanelBtn.change_rm(screens, rest_rm) RightPanelBtn.change_character_wrap(bikes_screen.ids['character_wrap_price'], bike_model.price) RightPanelBtn.change_character_wrap(bikes_screen.ids['character_wrap_power'], bike_model.power) RightPanelBtn.change_character_wrap(bikes_screen.ids['character_wrap_speed'], bike_model.speed) RightPanelBtn.change_character_wrap(bikes_screen.ids['character_wrap_acceleration'], bike_model.acceleration) RightPanelBtn.change_character_wrap(bikes_screen.ids['character_wrap_agility'], bike_model.agility) RightPanelBtn.cancel_animation_button(screens, 'left_panel_menu_bikes') self.init_item(menu_screen.init_bike) RightPanelBtn.change_bottom_right_btn(menu_screen) bikes_screen.ids['title'].color = UColor.hex(UColor.WHITE) else: Clock.schedule_once(self._create_animation_fail, 0) Clock.schedule_once(self._clear_animation, .5) elif 'MapsScreen' == _screen.__class__.__name__: if not app_config('map', 'title'): map_model = get_map_by_title(maps_screen.ids['title'].text) rest_rm = calc_rest_rm(map_model.price) if BaseLevel.buy(map_model): RightPanelBtn.change_rm(screens, rest_rm) RightPanelBtn.change_character_wrap(maps_screen.ids['character_wrap_price'], map_model.price) RightPanelBtn.change_character_wrap(maps_screen.ids['character_wrap_record'], '/dev/') RightPanelBtn.change_character_wrap(maps_screen.ids['character_wrap_level'], map_model.level) RightPanelBtn.change_character_wrap(maps_screen.ids['character_wrap_map'], map_model.map) RightPanelBtn.change_character_wrap(maps_screen.ids['character_wrap_total_way'], map_model.total_way) RightPanelBtn.cancel_animation_button(screens, 'left_panel_menu_maps') self.init_item(menu_screen.init_map) RightPanelBtn.change_bottom_right_btn(menu_screen) maps_screen.ids['title'].color = UColor.hex(UColor.WHITE) else: Clock.schedule_once(self._create_animation_fail, 0) Clock.schedule_once(self._clear_animation, .5) def init_item(self, cb_init): self.text = 'Ok' self.disabled = True cb_init() Clock.schedule_once(self._create_animation_success, 0) Clock.schedule_once(self._clear_animation, .4) def _create_animation_success(self, dt): bg = BgAnimation(widget=self._current_screen()) bg.anim_color(bg.rgba_success) def _create_animation_fail(self, dt): bg = BgAnimation(widget=self._current_screen()) bg.anim_color(bg.rgba_error) def _clear_animation(self, dt): bg = BgAnimation(widget=self._current_screen()) bg.anim_color(bg.rgba_default) @staticmethod def cancel_animation_button(screens, sid): [Animation.cancel_all(s.ids[sid], 'background_color') for s in screens] @staticmethod def change_rm(screens, value): for s in screens: GetObject.get_child(s, 'RMLayout').ids['panel_rm'].text = "{}: {}".format(Currency.units, value) return @staticmethod def change_character_wrap(character_wrap, value, color=UColor.hex(UColor.WHITE)): if type(value) is int: progress_bar = ValidObject.progress_bar(character_wrap.children[0].children[0]) character_wrap.value = value character_wrap.max = progress_bar.max = value if character_wrap.has_value: character_wrap.title = character_wrap.format_number() else: character_wrap.title = character_wrap.format_string() RightPanelBtn.prop_buttons_show(character_wrap) RightPanelBtn.change_color_labels_right_panel(character_wrap, color) @staticmethod def prop_buttons_show(character_wrap): btn = character_wrap.children[1].children[0] btn.disabled = not character_wrap.has_value btn.opacity = 1 @staticmethod def change_bottom_right_btn(menu_screen): if app_config('bike', 'title') and app_config('map', 'title'): menu_screen.ids['right_panel_btn'].text = 'Go' menu_screen.ids['right_panel_btn'].disabled = False @staticmethod def change_color_labels_right_panel(character_wrap, color): RightPanelBtn._change_color_labels(character_wrap.children[0], color) RightPanelBtn._change_color_labels(character_wrap.children[1], color) @staticmethod def _change_color_labels(wrap_children, color): for lbl in wrap_children.children[:]: if type(lbl) is Label: lbl.color = color
class FileViewItem(ToggleButtonBehavior, BoxLayout): name = StringProperty() source = StringProperty() is_all_folder = StringProperty(False) tmplWidget = ObjectProperty() def on_state(self, target, state): self.realise() if state == 'down': if self.name.lower().endswith('.kv'): f = Factory.get('FileItemOptionKV')() else: f = Factory.get('FileItemOption')() f.is_all_folder = self.is_all_folder self.add_widget(f) from kivy.animation import Animation anim = Animation(size_hint_y=0.2, duration=.1) anim.start(f) else: #remove the choice box self.remove_widget(self.children[0]) def on_press(self): if self.last_touch.is_double_tap: #directly add it to the pool if self.name.endswith('.bgp'): self.extract_package() elif self.name.endswith('.py'): print 'should I really execute this script ???' else: self.add_item("1", 'normal') def add_item(self, qt, verso): from os.path import relpath stack = App.get_running_app().root.ids['deck'].ids['stack'] ########################################################## qt = int(qt) if self.is_all_folder: #print self, self.source, self.name, self.is_all_folder #It is a folder, add all the imge from folder for name in [ x for x in os.listdir(self.is_all_folder) if x.endswith(FILE_FILTER) ]: ##for fiv in self.parent.children: ##if fiv.is_all_folder: continue if name.endswith(('.csv', '.xlsx')): continue box = StackPart() #box.name = fiv.name box.name = name box.source = os.path.join(self.is_all_folder, name) box.qt = qt box.verso = verso if name.endswith('.kv'): if self.is_all_folder.startswith(gamepath): fold = relpath(self.is_all_folder, gamepath) else: fold = self.is_all_folder box.template = "@%s" % os.path.join(fold, name) box.realise() stack.add_widget(box) from kivy.base import EventLoop EventLoop.idle() elif self.name.endswith('.csv'): App.get_running_app().root.ids.deck.load_file_csv(self.name) elif self.name.endswith('.xlsx'): App.get_running_app().root.ids.deck.load_file(self.name) elif self.name.endswith('.bgp'): self.extract_package() elif self.name.endswith('.py'): print 'should be executing', self.name, self.source, self.is_all_folder from imp import load_source Logger.info('Executing PYScript file %s' % self.name) m = self.name load_source(m[:-3], m) else: box = StackPart() box.name = self.name box.source = self.source box.qt = qt box.verso = verso stack.add_widget(box) if self.name.endswith('.kv'): box.tmplWidget = self.tmplWidget if self.name.startswith(gamepath): fold = relpath(self.name, gamepath) else: fold = self.name box.template = "@%s" % fold box.realise() def realise(self, use_cache=False, *args): if not self.name.endswith('.kv'): return #Force the creation of an image from self.template, thourhg real display from kivy.clock import Clock #Force the creaiotn of the tmpl miniture for display from template import BGTemplate try: Logger.info('[SGM] Realise FileItemView calling From File') tmpl = BGTemplate.FromFile(self.name, use_cache)[-1] except IndexError: Logger.warn('Warning: template file %s contains no Template !!' % self.name) from utils import alert alert('Error while loading %s template' % self.name) return #App.get_running_app().root.ids['realizer'].add_widget(tmpl) #force draw of the beast self.tmplWidget = tmpl def inner(*args): from kivy.base import EventLoop EventLoop.idle() cim = tmpl.toImage() cim.texture.flip_vertical() self.ids['img'].texture = cim.texture #App.get_running_app().root.ids['realizer'].remove_widget(tmpl) Clock.schedule_once(inner, -1) def extract_package(self): from zipfile import ZipFile import os import tempfile zip_name = self.name zip_path = os.path.abspath(zip_name) temp_dir = tempfile.mkdtemp(prefix='BGM_%s' % os.path.split(self.name)[-1]) from kivy.resources import resource_add_path resource_add_path(temp_dir) with ZipFile(zip_path, 'r') as zip_file: # Build a list of only the members below ROOT_PATH members = zip_file.namelist() # Extract only those members to the temp directory zip_file.extractall(temp_dir, members) from kivy.app import App dm = App.get_running_app().root.ids.deck #Looping on all mebers, resolving differntly depending on file type for m in members: if m.endsiwth('.kv'): m = os.path.join(temp_dir, m) Logger.info('Registering Package Template %s' % m) from template import templateList templateList.register_file(os.path.join(temp_dir, m)) #then deck elif m.endswith('.csv'): m = os.path.join(temp_dir, m) Logger.info('Loading CSV File %s' % m) dm.load_file_csv(os.path.join(temp_dir, m)) elif m.endwith('.xlsx'): m = os.path.join(temp_dir, m) Logger.info('Loading XLSX File %s' % m) dm.load_file(os.path.join(temp_dir, m)) elif m.endswith('.py'): #First python from imp import load_source m = os.path.join(temp_dir, m) Logger.info('Executing Package file %s' % m) load_source(m[:-3], m) from utils import start_file start_file(temp_dir) #Ensure the last CSV file is not saved, as it is parts of a package dm.record_last_file('')
class ColorPicker(RelativeLayout): ''' See module documentation. ''' font_name = StringProperty('data/fonts/DroidSansMono.ttf') '''Specifies the font used used on the Color Picker :data:`font_name` is an :class:`~kivy.properties.StringProperty` defaults to 'data/fonts/DroidSansMono.ttf' ''' color = ListProperty((1, 1, 1, 1)) '''The :data:`color` holds the color currently selected in rgba format. :data:`color` is an :class:`~kivy.properties.ListProperty` defaults to (1, 1, 1, 1) ''' hsv = ListProperty((1, 1, 1)) '''The :data:`hsv` holds the color currently selected in hsv format. :data:`hsv` is an :class:`~kivy.properties.ListProperty` defaults to (1, 1, 1) ''' def _get_hex(self): return get_hex_from_color(self.color) def _set_hex(self, value): self.color = get_color_from_hex(value)[:4] hex_color = AliasProperty(_get_hex, _set_hex, bind=('color', )) '''The :data:`hex_color` holds the currently selected color in hex. :data:`hex_color` is a :class:`~kivy.properties.AliasProperty` default to `#ffffffff` ''' wheel = ObjectProperty(None) '''The :data:`wheel` holds the color wheel. :data:`wheel` is an :class:`~kivy.properties.ObjectProperty` defaults to None ''' # now used only internally. foreground_color = ListProperty((1, 1, 1, 1)) def on_color(self, instance, value): if not self._updating_clr: self._updating_clr = True self.hsv = rgb_to_hsv(*value[:3]) self._updating_clr = False def on_hsv(self, instance, value): if not self._updating_clr: self._updating_clr = True self.color[:3] = hsv_to_rgb(*value) self._updating_clr = False def _trigger_update_clr(self, mode, clr_idx, text): self._upd_clr_list = mode, clr_idx, text Clock.unschedule(self._update_clr) Clock.schedule_once(self._update_clr) def _update_clr(self, dt): mode, clr_idx, text = self._upd_clr_list try: text = max(0, min(254, float(text))) if mode == 'rgb': self.color[clr_idx] = float(text) / 255. else: self.hsv[clr_idx] = float(text) / 255. except ValueError: Logger.warning('Color Picker: invalid value : {}'.format(text)) def _update_hex(self, dt): if len(self._upd_hex_list) != 9: return self.hex_color = self._upd_hex_list def _trigger_update_hex(self, text): self._upd_hex_list = text Clock.unschedule(self._update_hex) Clock.schedule_once(self._update_hex) def __init__(self, **kwargs): self._updating_clr = False super(ColorPicker, self).__init__(**kwargs)
class ArrowWidget(Widget): """A widget that points from one :class:`~LiSE.gui.board.Spot` to another. :class:`Arrow`s are the graphical representations of :class:`~LiSE.model.Portal`s. They point from the :class:`Spot` representing the :class:`Portal`'s origin, to the one representing its destination. """ board = ObjectProperty() name = StringProperty() margin = NumericProperty(10) """When deciding whether a touch collides with me, how far away can the touch get before I should consider it a miss?""" w = NumericProperty(2) """The width of the inner, brighter portion of the :class:`Arrow`. The whole :class:`Arrow` will end up thicker.""" pawns_here = ListProperty([]) trunk_points = ListProperty([]) head_points = ListProperty([]) points = ReferenceListProperty(trunk_points, head_points) trunk_quad_vertices_bg = ListProperty([0] * 8) trunk_quad_vertices_fg = ListProperty([0] * 8) left_head_quad_vertices_bg = ListProperty([0] * 8) right_head_quad_vertices_bg = ListProperty([0] * 8) left_head_quad_vertices_fg = ListProperty([0] * 8) right_head_quad_vertices_fg = ListProperty([0] * 8) slope = NumericProperty(0.0, allownone=True) y_intercept = NumericProperty(0) origin = ObjectProperty() destination = ObjectProperty() repointed = BooleanProperty(True) bg_scale_unselected = NumericProperty(4) bg_scale_selected = NumericProperty(5) selected = BooleanProperty(False) hit = BooleanProperty(False) bg_color_unselected = ListProperty() bg_color_selected = ListProperty() fg_color_unselected = ListProperty() fg_color_selected = ListProperty() arrowhead_size = NumericProperty(10) collide_radius = NumericProperty(3) collider = ObjectProperty() portal = ObjectProperty() def on_portal(self, *args): """Set my ``name`` and instantiate my ``mirrormap`` as soon as I have the properties I need to do so. """ if not (self.board and self.origin and self.destination and self.origin.name in self.board.character.portal and self.destination.name in self.board.character.portal): Clock.schedule_once(self.on_portal, 0) return self.name = '{}->{}'.format(self.portal['origin'], self.portal['destination']) def collide_point(self, x, y): """Delegate to my ``collider``, or return ``False`` if I don't have one. """ if not self.collider: return False return (x, y) in self.collider def __init__(self, **kwargs): """Create trigger for my _repoint method. Delegate to parent for everything else. """ self._trigger_repoint = Clock.create_trigger(self._repoint, timeout=-1) super().__init__(**kwargs) def on_origin(self, *args): """Make sure to redraw whenever the origin moves.""" if self.origin is None: Clock.schedule_once(self.on_origin, 0) return self.origin.bind(pos=self._trigger_repoint, size=self._trigger_repoint) def on_destination(self, *args): """Make sure to redraw whenever the destination moves.""" if self.destination is None: Clock.schedule_once(self.on_destination, 0) return self.destination.bind(pos=self._trigger_repoint, size=self._trigger_repoint) def on_board(self, *args): """Draw myself for the first time as soon as I have the properties I need to do so. """ if None in (self.board, self.origin, self.destination): Clock.schedule_once(self.on_board, 0) return self._trigger_repoint() def add_widget(self, wid, index=0, canvas=None): """Put the :class:`Pawn` at a point along my length proportionate to how close it is to finishing its travel through me. Only :class:`Pawn` should ever be added as a child of :class:`Arrow`. """ super().add_widget(wid, index, canvas) if not hasattr(wid, 'group'): return wid._no_use_canvas = True mycanvas = (self.canvas.before if canvas == 'before' else self.canvas.after if canvas == 'after' else self.canvas) mycanvas.remove(wid.canvas) pawncanvas = (self.board.pawnlayout.canvas.before if canvas == 'before' else self.board.pawnlayout.canvas.after if canvas == 'after' else self.board.pawnlayout.canvas) for child in self.children: if hasattr(child, 'group') and child.group in pawncanvas.children: pawncanvas.remove(child.group) pawncanvas.add(child.group) self.pospawn(wid) def remove_widget(self, wid): """Remove the special :class:`InstructionGroup` I was using to draw this :class:`Pawn`. """ super().remove_widget(wid) wid._no_use_canvas = False def on_points(self, *args): """Reposition my children when I have new points.""" for pawn in self.children: self.pospawn(pawn) def pos_along(self, pct): """Return coordinates for where a Pawn should be if it has travelled along ``pct`` of my length (between 0 and 1). Might get complex when I switch over to using beziers for arrows, but for now this is quite simple, using distance along a line segment. """ if pct < 0 or pct > 1: raise ValueError("Invalid portion") (ox, oy) = self.origin.center (dx, dy) = self.destination.center xdist = (dx - ox) * pct ydist = (dy - oy) * pct return (ox + xdist, oy + ydist) def pospawn(self, pawn): """Position a :class:`Pawn` that is my child so as to reflect how far its :class:`Thing` has gone along my :class:`Portal`. """ if self.board.tick < pawn.thing['arrival_time']: # It's weird that the pawn is getting placed in me, but # I'll do my best.. pawn.pos = self.pos_along(0) return elif (pawn.thing['next_arrival_time'] and self.board.tick >= pawn.thing['next_arrival_time']): pawn.pos = self.pos_along(1) return try: pawn.pos = self.pos_along( (self.board.tick - pawn.thing['arrival_time']) / (pawn.thing['next_arrival_time'] - pawn.thing['arrival_time'])) except (TypeError, ZeroDivisionError): pawn.pos = self.pos_along(0) def _get_points(self): """Return the coordinates of the points that describe my shape.""" orig = self.origin dest = self.destination (ox, oy) = orig.center ow = orig.width if hasattr(orig, 'width') else 0 taillen = float(self.arrowhead_size) ory = ow / 2 (dx, dy) = dest.center (dw, dh) = dest.size if hasattr(dest, 'size') else (0, 0) dry = dh / 2 return get_points(ox, oy, ory, dx, dy, dry, taillen) def _get_slope(self): """Return a float of the increase in y divided by the increase in x, both from left to right.""" orig = self.origin dest = self.destination ox = orig.x oy = orig.y dx = dest.x dy = dest.y if oy == dy: return 0 elif ox == dx: return None else: rise = dy - oy run = dx - ox return rise / run def _get_b(self): """Return my Y-intercept. I probably don't really hit the left edge of the window, but this is where I would, if I were long enough. """ orig = self.origin dest = self.destination (ox, oy) = orig.pos (dx, dy) = dest.pos denominator = dx - ox x_numerator = (dy - oy) * ox y_numerator = denominator * oy return ((y_numerator - x_numerator), denominator) def _repoint(self, *args): """Recalculate points, y-intercept, and slope""" if None in (self.origin, self.destination): Clock.schedule_once(self._repoint, 0) return (self.trunk_points, self.head_points) = self._get_points() (ox, oy, dx, dy) = self.trunk_points r = self.w / 2 bgr = r * self.bg_scale_selected if self.selected \ else self.bg_scale_unselected self.trunk_quad_vertices_bg = get_thin_rect_vertices( ox, oy, dx, dy, bgr) self.collider = Collide2DPoly(self.trunk_quad_vertices_bg) self.trunk_quad_vertices_fg = get_thin_rect_vertices(ox, oy, dx, dy, r) (x1, y1, endx, endy, x2, y2) = self.head_points self.left_head_quad_vertices_bg = get_thin_rect_vertices( x1, y1, endx, endy, bgr) self.right_head_quad_vertices_bg = get_thin_rect_vertices( x2, y2, endx, endy, bgr) self.left_head_quad_vertices_fg = get_thin_rect_vertices( x1, y1, endx, endy, r) self.right_head_quad_vertices_fg = get_thin_rect_vertices( x2, y2, endx, endy, r) self.slope = self._get_slope() self.y_intercept = self._get_b() self.repointed = True
class MDCheckbox(CircularRippleBehavior, ToggleButtonBehavior, MDIcon): active = BooleanProperty(False) checkbox_icon_normal = StringProperty("checkbox-blank-outline") checkbox_icon_down = StringProperty("checkbox-marked-outline") radio_icon_normal = StringProperty("checkbox-blank-circle-outline") radio_icon_down = StringProperty("checkbox-marked-circle-outline") selected_color = ListProperty() unselected_color = ListProperty() disabled_color = ListProperty() _current_color = ListProperty([0.0, 0.0, 0.0, 0.0]) def __init__(self, **kwargs): self.check_anim_out = Animation(font_size=0, duration=0.1, t="out_quad") self.check_anim_in = Animation( font_size=sp(24), duration=0.1, t="out_quad" ) super().__init__(**kwargs) self.selected_color = self.theme_cls.primary_color self.unselected_color = self.theme_cls.secondary_text_color self.disabled_color = self.theme_cls.divider_color self._current_color = self.unselected_color self.check_anim_out.bind( on_complete=lambda *x: self.check_anim_in.start(self) ) self.bind( checkbox_icon_normal=self.update_icon, checkbox_icon_down=self.update_icon, radio_icon_normal=self.update_icon, radio_icon_down=self.update_icon, group=self.update_icon, selected_color=self.update_color, unselected_color=self.update_color, disabled_color=self.update_color, disabled=self.update_color, state=self.update_color, ) self.update_icon() self.update_color() def update_icon(self, *args): if self.state == "down": self.icon = ( self.radio_icon_down if self.group else self.checkbox_icon_down ) else: self.icon = ( self.radio_icon_normal if self.group else self.checkbox_icon_normal ) def update_color(self, *args): if self.disabled: self._current_color = self.disabled_color elif self.state == "down": self._current_color = self.selected_color else: self._current_color = self.unselected_color def on_state(self, *args): if self.state == "down": self.check_anim_in.cancel(self) self.check_anim_out.start(self) self.update_icon() self.active = True else: self.check_anim_in.cancel(self) self.check_anim_out.start(self) self.update_icon() self.active = False def on_active(self, *args): self.state = "down" if self.active else "normal"
class ICDialog(ThemableBehavior, RectangularElevationBehavior, ModalView): """ Dialog box with the ability to add action buttons """ title = StringProperty('KivyIC Dialog Box') ''' Title of the Dialog Box. :attr:`title` is an :class:`~kivy.properties.StringProperty` and defaults to 'KivyIC Dialog Box'. .. versionadded:: 0.1 ''' content = ObjectProperty(None) ''' Container widget for what will be displayed in the Dialog Box. :attr:`content` is an :class:`~kivy.properties.ObjectProperty` and defaults to None. .. versionadded:: 0.1 ''' content_fit = OptionProperty('items', options=['window', 'items']) md_bg_color = ListProperty([0, 0, 0, .2]) _container = ObjectProperty() _action_buttons = ListProperty([]) _action_area = ObjectProperty() action_area_width = NumericProperty() def __init__(self, **kwargs): super().__init__(**kwargs) self.bind(_action_buttons=self._update_action_buttons, auto_dismiss=lambda *x: setattr( self.shadow, 'on_release', self.shadow.dismiss if self.auto_dismiss else None)) def add_action_button(self, text, action=None): """Add an :class:`FlatButton` to the right of the action area. :param icon: Unicode character for the icon :type icon: str or None :param action: Function set to trigger when on_release fires :type action: function or None """ button = MDFlatButton(text=text, size_hint=(None, None), height=dp(36)) if action: button.bind(on_release=action) # FIX - fix color button.text_color = self.theme_cls.primary_color button.md_bg_color = self.theme_cls.bg_light self._action_buttons.append(button) def add_widget(self, widget): if self._container: if self.content: raise PopupException( 'Popup can have only one widget as content') self.content = widget else: super(ICDialog, self).add_widget(widget) def open(self, *largs): '''Show the view window from the :attr:`attach_to` widget. If set, it will attach to the nearest window. If the widget is not attached to any window, the view will attach to the global :class:`~kivy.core.window.Window`. ''' if self._window is not None: Logger.warning('ModalView: you can only open once.') return self # search window self._window = self._search_window() if not self._window: Logger.warning('ModalView: cannot open view, no window found.') return self self._window.add_widget(self) self._window.bind(on_resize=self._align_center, on_keyboard=self._handle_keyboard) self.center = self._window.center self.bind(size=self._align_center) a = Animation(_anim_alpha=1., d=self._anim_duration) a.bind(on_complete=lambda *x: self.dispatch('on_open')) a.start(self) return self def dismiss(self, *largs, **kwargs): '''Close the view if it is open. If you really want to close the view, whatever the on_dismiss event returns, you can use the *force* argument: :: view = ModalView(...) view.dismiss(force=True) When the view is dismissed, it will be faded out before being removed from the parent. If you don't want animation, use:: view.dismiss(animation=False) ''' if self._window is None: return self if self.dispatch('on_dismiss') is True: if kwargs.get('force', False) is not True: return self if kwargs.get('animation', True): Animation(_anim_alpha=0., d=self._anim_duration).start(self) else: self._anim_alpha = 0 self._real_remove_widget() return self def on_content(self, instance, value): if self._container: self._container.clear_widgets() self._container.add_widget(value) def on__container(self, instance, value): if value is None or self.content is None: return self._container.clear_widgets() self._container.add_widget(self.content) def on_touch_down(self, touch): if self.disabled and self.collide_point(*touch.pos): return True return super(ICDialog, self).on_touch_down(touch) def _update_action_buttons(self, *args): self._action_area.clear_widgets() self.action_area_width = 0 for btn in self._action_buttons: btn.content.texture_update() btn.width = btn.content.texture_size[0] + dp(16) self.action_area_width += btn.width self._action_area.add_widget(btn) spacing = sum(self._action_area.spacing) - self._action_area.spacing[0] self.action_area_width += spacing
class DialogOKDismiss(ICDialog): """ Ok - Dismiss Dialog with Input Text field Parameters: text: str: value of input text field helper_text: str: helper text to provide feedback to user hint_text: str: to provide feedback to user Usage: bind to response to determine user action, true if click OK false if Dismiss bind to text to get information typed """ text = StringProperty() ''' String in the Input Text Field. Can be used to set of retrieve data. :data:`text` is an :class:`~kivy.properties.StringProperty`, defaults to ''. .. version added:: 0.1 ''' hint_text = StringProperty() ''' Text that will show in text input prior to receiving focus, setting mode to persistant will cause to raise above input line while user is typing. :data:`hint_text` is an :class:`~kivy.properties.StringProperty`, defaults to ''. .. version added:: 0.1 ''' helper_text = StringProperty() ''' Text that will show below text input. Text will stay before and after focus :data:`helper_text` is an :class:`~kivy.properties.StringProperty`, defaults to ''. .. version added:: 0.1 ''' response = BooleanProperty() ''' Stores action of user when exiting dialog. True if user clicks ok. False if user clicks Dismiss. :data:`response` is an :class:`~kivy.properties.BooleanProperty`, .. version added:: 0.1 ''' def __init__(self, **kwargs): super(DialogOKDismiss, self).__init__(**kwargs) content = InputDialog(hint_text=self.hint_text, helper_text=self.helper_text) self.bind(text=self.setter(content.text)) self.content = content self.add_action_button("OK", action=lambda *x: self.click(True)) self.add_action_button("Dismiss", action=lambda *x: self.click(False)) self.content.focus = True def click(self, response): self.response = response self.dismiss()
class FileExplorerDialog(ICDialog): title = StringProperty('File Explorer Dialog') ''' Title of the dialog window. :data:`title` is an :class:`~kivy.properties.StringProperty`, defaults to 'File Explorer Dialog'. .. version added:: 0.1 ''' # FIX - works on windows, need to set to $Home on Linux initial_directory = StringProperty() ''' Starting directory for file explorer. :data:`initial_directory` is an :class:`~kivy.properties.StringProperty`, defaults to 'C:/Users/<user name>/' for windows, $HOME for Linux. .. version added:: 0.1 ''' filter = ListProperty() ''' Filter to apply to files shown in file view. :data:`filter` is an :class:`~kivy.properties.ListProperty`, defaults to '*.*'. .. version added:: 0.1 ''' # TODO -- v 0.2 - add ability to select multiple files file_name_s = StringProperty() ''' Name of selected file(s). If multiple files are selected, each file will be separated by a comma. :data:`file_name_s` is an :class:`~kivy.properties.StringProperty`, defaults to ''. .. version added:: 0.1 ''' def __init__(self, **kwargs): super(FileExplorerDialog, self).__init__(**kwargs) user_path = os.path.join(get_home_directory(), 'Documents') file_explorer = FileExplorer(select_string='Select', favorites=[(user_path, 'Documents')]) file_explorer.bind(on_success=self._fbrowser_success, on_canceled=self._fbrowser_canceled, on_submit=self._fbrowser_submit) file_explorer.file_selection_container.clear_widgets() self.content = file_explorer self.add_action_button( "Dismiss", action=lambda *x: file_explorer.dispatch('on_canceled')) self.add_action_button( "OK", action=lambda *x: file_explorer.dispatch('on_success')) Clock.schedule_once(partial(self._post_init, file_explorer)) def _post_init(self, file_explorer, *args): file_explorer.filter_button.width = self.action_area_width def _fbrowser_canceled(self, instance): self.dismiss() def _fbrowser_success(self, instance): # ERR - self.file_name_s = instance.selection[0] / IndexError: list index out of range self.file_name_s = instance.selection[0] self.dismiss() def _fbrowser_submit(self, instance): self.file_name_s = instance.selection[0] self.dismiss() def add_button(self, buttons): """ add action butttons via a dict :param buttons: dict: {button name: action} :return: None """ for text, action in buttons.items(): self.add_action_button(text, action=lambda *x: action())
class DraggableObjectBehavior(object): """A widget that inherits from this class can participate in a drag by someone dragging it with the mouse. """ drag_controller = ObjectProperty(None) """A (potentially global) :class:`DraggableController` instance that manages the (potential) drag. If `None` during the first potential drag, a :class:`DraggableController` instance will be created and set. """ drag_widget = ObjectProperty(None) """The widget, whose texture will be copied and previewed as the object is dragged. If `None`, it's this widget. """ drag_cls = StringProperty('') """A name to determine where we can potentially drop the dragged widget. For each :class:`DraggableLayoutBehavior` that we hover over, if :attr:`drag_cls` is in that :attr:`DraggableLayoutBehavior.drag_classes`, we will preview and allow the widget to be dropped there. """ _drag_touch = None def initiate_drag(self): """Called by the :class:`DraggableController`, when a drag is initiated on the widget (i.e. thw widget is actually being dragged once it exceeds the minimum drag distance). """ pass def complete_drag(self): """Called by the :class:`DraggableController`, when a drag is completed. """ pass def _touch_uid(self): return '{}.{}'.format(self.__class__.__name__, self.uid) def on_touch_down(self, touch): uid = self._touch_uid() if uid in touch.ud: return touch.ud[uid] if super(DraggableObjectBehavior, self).on_touch_down(touch): touch.ud[uid] = False return True x, y = touch.pos if not self.collide_point(x, y): touch.ud[uid] = False return False if self._drag_touch or ('button' in touch.profile and touch.button.startswith('scroll')): touch.ud[uid] = False return False self._drag_touch = touch touch.grab(self) touch.ud[uid] = True if not self.drag_controller: self.drag_controller = DraggableController() return self.drag_controller.drag_down(self, touch) def on_touch_move(self, touch): uid = self._touch_uid() if uid not in touch.ud: touch.ud[uid] = False return super(DraggableObjectBehavior, self).on_touch_move(touch) if not touch.ud[uid]: return super(DraggableObjectBehavior, self).on_touch_move(touch) if touch.grab_current is not self: return False if not self.drag_controller: self.drag_controller = DraggableController() return self.drag_controller.drag_move(self, touch) def on_touch_up(self, touch): uid = self._touch_uid() if uid not in touch.ud: touch.ud[uid] = False return super(DraggableObjectBehavior, self).on_touch_up(touch) if not touch.ud[uid]: return super(DraggableObjectBehavior, self).on_touch_up(touch) if touch.grab_current is not self: return False touch.ungrab(self) self._drag_touch = None if not self.drag_controller: self.drag_controller = DraggableController() return self.drag_controller.drag_up(self, touch)
class TemplateEditTree(TreeView): "Use in Template Edit Popup to display all possible fields" tmplPath = StringProperty(allownone=True) current_selection = ObjectProperty() values = DictProperty() #values from template, if any tmplDict = DictProperty() #link a tmplWidget to a node def update_tmpl(self, tmpl): if self.target: self.target.clear_widgets() self.target.add_widget(tmpl) tmpl.pos = self.target.pos self.current_selection = (tmpl, self.selected_node) def on_tmplPath(self, instance, value): self.current_selection = () self.clear_widgets() self.get_root().nodes = list() if not value: return from template import BGTemplate #tmplPath is in the form [NAME][@PATH]. If path provided, load all tmpl from there. Without it, take name from library name, path = self.tmplPath.split('@') if not (name) and not (path): Logger.warn( 'Warning: tmpl Path is empty. stopping template edition') if not path: from template import templateList tmpls = [templateList[name]] else: tmpls = BGTemplate.FromFile(self.tmplPath, use_cache=True) for tmpl in tmpls: tmpl.apply_values(self.values) node = self.load_tmpl(tmpl) self.select_node(node) # will issue a template update def load_tmpl(self, tmpl): #Now add on load node = self.add_node( TreeViewLabel(text=tmpl.template_name, color_selected=(.6, .6, .6, .8))) node.is_leaf = False #add the thingy #point to the template node.template = tmpl self.tmplDict[tmpl] = node #Deal with Template Properties: for pname, editor in sorted(tmpl.vars.items()): self.add_node(TreeViewField(name=pname, editor=editor(tmpl)), node) #Deal with KV style elemebts for fname in sorted(tmpl.ids.keys()): if not isinstance(tmpl.ids[fname], BaseField): continue _wid = tmpl.ids[fname] if not _wid.editable: continue if _wid.default_attr: w = _wid.params[_wid.default_attr](_wid) if w is not None: #None when not editable self.add_node( TreeViewField(pre_label=fname, name=_wid.default_attr, editor=w), node) self.toggle_node(node) return node
class SelectionTool(BoxLayout): library_directory = f"{dirname(__file__)}/Example_Data" book_id = StringProperty() page = StringProperty() def Ss(self): timestr = time.strftime("%Y%m%d_%H%M%S") self.export_to_png(f"{dirname(__file__)}\word\IMG_" + timestr + ".png") # print(timestr) def __init__(self): super(SelectionTool, self).__init__() self.image_pane.bind(on_store_rectangles=self.store_rectangles) self.all_rectangles = {} self.rectangles_filename = 'rectangles.json' self.load_rectangles() book_pattern = os.path.join(self.library_directory, '[0-9]' * 4) self.book_selector.values = [ os.path.basename(s) for s in glob.glob(book_pattern) ] self.book_selector.text = self.book_selector.values[ 0] if self.book_selector.values else 'No Books' self.on_book_id() def on_book_id(self, inst=None, value=None): image_pattern = os.path.join(self.library_directory, self.book_id, '*.jpg') self.page_selector.values = [ os.path.basename(s)[:-4] for s in glob.glob(image_pattern) ] self.page_selector.text = self.page_selector.values[ 0] if self.page_selector.values else 'No Images' # self.word_list.clear_widgets() # with open(os.path.join(self.library_directory, self.book_id, 'word_list.txt')) as fp: # for word in sorted(fp.readlines()): # self.word_list.add_widget(Label(text=word.strip())) # self.color_word_list() self.on_page() def on_page(self, *_): page_filename = self.page + '.jpg' self.image_pane.source = os.path.join(self.library_directory, self.book_id, page_filename) self.image_pane.clear_rectangles() try: for rect in self.all_rectangles[self.book_id][self.page]: rect.compute_screen_coordinates() self.image_pane.add_new_rectangle(rect) except KeyError: pass def store_rectangles(self, sender=None, rectangles=[]): if self.book_id not in self.all_rectangles: self.all_rectangles[self.book_id] = {} self.all_rectangles[self.book_id].update( {self.page: [r for r in rectangles]}) self.color_word_list() self.save_rectangles() def load_rectangles(self): self.all_rectangles = {} try: with open(self.rectangles_filename) as fd: all_rectangles_dict = json.load(fd) for book_id, book_rectangles in all_rectangles_dict.items(): self.all_rectangles[book_id] = {} for page, rectangles in book_rectangles.items(): self.all_rectangles[book_id][page] = \ [SelectionBox(image_pane=self.image_pane, **rect) for rect in rectangles] except IOError: print("Can't find rectangles file!") def save_rectangles(self): rectangle_dict = {} for book_id, book_rectangles in self.all_rectangles.items(): for page, rectangles in book_rectangles.items(): page_dict = {page: [rect.to_dict() for rect in rectangles]} if rectangles: rectangle_dict[book_id] = rectangle_dict.get(book_id, {}) rectangle_dict[book_id].update(page_dict) with open(self.rectangles_filename, 'w') as fd: json.dump(rectangle_dict, fd, sort_keys=True, indent=4, separators=(',', ': ')) def color_word_list(self): if self.book_id in self.all_rectangles: rectangle_labels = [ rect.label.text for page_rects in self.all_rectangles[self.book_id].values() for rect in page_rects ] else: rectangle_labels = [] # for label in self.word_list.children: # label.color = (0, 1, 0, 1) if label.text in rectangle_labels else (1, 1, 1, 1) def __init__(self): super(SelectionTool, self).__init__() self.image_pane.bind(on_store_rectangles=self.store_rectangles) self.all_rectangles = {} if kivy.platform == 'ios': # added self.rectangles_filename = os.path.join( App.get_running_app().user_data_dir, 'rectangles.json') else: self.rectangles_filename = 'rectangles.json' self.load_rectangles() book_pattern = os.path.join(self.library_directory, '[0-9]' * 4) self.book_selector.values = [ os.path.basename(s) for s in glob.glob(book_pattern) ] self.book_selector.text = self.book_selector.values[ 0] if self.book_selector.values else 'No Books'
class Widget(KivyWidget): """MPF-MC Widget class. The :class:`Widget` class is the base class required for creating Widgets for use in the media controller. It is based on the Kivy kivy.uix.widget.Widget class, but has some custom behavior for use in the MC. The most important detail is every widget is contained inside another specialized widget class (mpfmc.uix.widget.WidgetContainer). This container class is always the parent of a MC Widget and provides the coordinate translations to allow MC widgets to use their anchor point coordinates instead of the bottom-left corner for all coordinate settings (x, y, pos). The WidgetContainer is automatically created when a widget is created and should not be manipulated directly. It is important to remember when walking the widget tree the WidgetContainer is the Widget's parent. """ widget_type_name = '' # Give this a name in your subclass, e.g. 'Image' # We loop through the keys in a widget's config dict and check to see if # the widget's base class has attributes for them, and if so, we set # them. This is how any attribute from the base class can be exposed via # our configs. However we use some config keys that Kivy also uses, # and we use them for different purposes, so there are some keys that we # use that we never want to set on widget base classes. _dont_send_to_kivy = ('x', 'y', 'key') merge_settings = tuple() animation_properties = list() """List of properties for this widget that may be animated using widget animations.""" def __init__(self, mc: "MpfMc", config: Optional[dict]=None, key: Optional[str]=None, **kwargs) -> None: del kwargs self._container = None self.size_hint = (None, None) # Needs to be deepcopy since configs can have nested dicts self.config = deepcopy(config) super().__init__(**self.pass_to_kivy_widget_init()) self.mc = mc # Create a container widget as this widget's parent. The container will adjust # the coordinate system for this widget so that all positional properties are # based on the widget's anchor rather than the lower left corner. self._container = WidgetContainer(self, z=self.config['z']) self._container.add_widget(self) self._container.fbind('parent', self.on_container_parent) self.animation = None self._animation_event_keys = set() # MPF event keys for event handlers that have been registered for # animation events. Used to remove the handlers when this widget is # removed. self._pre_animated_settings = dict() # dict of original values of settings that were animated so we can # restore them later self._percent_prop_dicts = dict() self._default_style = None self._set_default_style() self._apply_style() if 'color' in self.config and not isinstance(self.config['color'], RGBAColor): self.config['color'] = RGBAColor(self.config['color']) # Set initial attribute values from config for k, v in self.config.items(): if k not in self._dont_send_to_kivy and hasattr(self, k): setattr(self, k, v) # Has to be after we set the attributes since it could be in the config self.key = key # Build animations if 'animations' in self.config and self.config['animations']: for k, v in self.config['animations'].items(): if k == 'add_to_slide': # needed because the initial properties of the widget # aren't set yet Clock.schedule_once(self.on_add_to_slide, -1) elif k not in magic_events: self._register_animation_events(k) else: self.config['animations'] = dict() # why is this needed? Why is it not config validated by here? todo if 'reset_animations_events' in self.config: for event in [x for x in self.config['reset_animations_events'] if x not in magic_events]: self._animation_event_keys.add(self.mc.events.add_handler( event=event, handler=self.reset_animations)) # Set widget expiration (if configured) self.expire = config.get('expire', None) if self.expire: self.schedule_removal(self.expire) def __repr__(self) -> str: # pragma: no cover return '<{} Widget id={}>'.format(self.widget_type_name, self.id) def pass_to_kivy_widget_init(self) -> dict: """Initializes the dictionary of settings to pass to Kivy.""" return dict() def merge_asset_config(self, asset) -> None: for setting in [x for x in self.merge_settings if ( x not in self.config['_default_settings'] and x in asset.config)]: self.config[setting] = asset.config[setting] def on_anchor_offset_pos(self, instance, pos): """Called whenever the anchor_offset_pos property value changes.""" del instance if self.parent: self.parent.pos = pos def on_container_parent(self, instance, parent): del instance if parent: # some attributes can be expressed in percentages. This dict holds # those, key is attribute name, val is max value self._percent_prop_dicts = dict(x=parent.width, y=parent.height, width=parent.width, height=parent.height, opacity=1, line_height=1) self.pos = self.calculate_initial_position(parent.width, parent.height, self.config['x'], self.config['y']) # pylint: disable-msg=too-many-arguments # pylint: disable-msg=too-many-statements @staticmethod def calculate_initial_position(parent_w: int, parent_h: int, x: Optional[Union[int, str]] = None, y: Optional[Union[int, str]] = None) -> tuple: """Returns the initial x,y position for the widget within a larger parent frame based on several positioning parameters. This position will be combined with the widget anchor position to determine its actual position on the screen. Args: parent_w: Width of the parent frame. parent_h: Height of the parent frame. x: (Optional) Specifies the x (horizontal) position of the widget from the left edge of the slide. Can be a numeric value which represents the actual x value, or can be a percentage (string with percent sign, like '20%') which is set taking into account the size of the parent width. (e.g. parent width of 800 with x='20%' results in x=160. Can also be negative to position the widget partially off the left of the slide. Default value of None will return the horizontal center (parent width / 2). Can also start with the strings "left", "center", or "right" which can be combined with values. (e.g right-2, left+4, center-1) y: (Optional) Specifies the y (vertical) position of the widget from the bottom edge of the slide. Can be a numeric value which represents the actual y value, or can be a percentage (string with percent sign, like '20%') which is set taking into account the size of the parent height. (e.g. parent height of 600 with y='20%' results in y=120. Can also be negative to position the widget partially off the bottom of the slide. Default value of None will return the vertical center (parent height / 2). Can also start with the strings "top", "middle", or "bottom" which can be combined with values. (e.g top-2, bottom+4, middle-1) Returns: Tuple of x, y coordinates for the lower-left corner of the widget you're placing. See the widgets documentation for examples. """ # Set defaults if x is None: x = 'center' if y is None: y = 'middle' # ---------------------- # X / width / horizontal # ---------------------- # Set position if isinstance(x, str): x = str(x).replace(' ', '') start_x = 0 if x.startswith('right'): x = x.strip('right') start_x = parent_w elif x.startswith('middle'): x = x.strip('middle') start_x = parent_w / 2 elif x.startswith('center'): x = x.strip('center') start_x = parent_w / 2 elif x.startswith('left'): x = x.strip('left') if not x: x = '0' x = percent_to_float(x, parent_w) x += start_x # -------------------- # Y / height / vertical # -------------------- # Set position if isinstance(y, str): y = str(y).replace(' ', '') start_y = 0 if y.startswith('top'): y = y.strip('top') start_y = parent_h elif y.startswith('middle'): y = y.strip('middle') start_y = parent_h / 2 elif y.startswith('center'): y = y.strip('center') start_y = parent_h / 2 elif y.startswith('bottom'): y = y.strip('bottom') if not y: y = '0' y = percent_to_float(y, parent_h) y += start_y return x, y def _set_default_style(self) -> None: """Sets the default widget style name.""" if ('{}_default'.format(self.widget_type_name.lower()) in self.mc.machine_config['widget_styles']): self._default_style = self.mc.machine_config['widget_styles'][ '{}_default'.format(self.widget_type_name.lower())] def _apply_style(self, force_default: bool=False) -> None: """Apply any style to the widget that is specified in the config.""" if not self.config['style'] or force_default: if self._default_style: style = self._default_style else: return else: try: style = self.mc.machine_config['widget_styles'][self.config['style'].lower()] except KeyError: raise ValueError("{} has an invalid style name: {}".format( self, self.config['style'].lower())) found = False try: # This looks crazy but it's not too bad... The list comprehension # builds a list of attributes (settings) that are in the style # definition but that were not manually set in the widget. # Then it sets the attributes directly since the config was already # processed. for attr in [x for x in style if x not in self.config['_default_settings']]: self.config[attr] = style[attr] found = True except (AttributeError, KeyError): pass if not found and not force_default: self._apply_style(force_default=True) def prepare_for_removal(self) -> None: """Prepare the widget to be removed.""" self.mc.clock.unschedule(self.remove) self._remove_animation_events() def schedule_removal(self, secs: float) -> None: """Schedule the widget to be removed after the specified number of seconds have elapsed.""" self.mc.clock.schedule_once(self.remove, secs) def remove(self, *dt) -> None: """Perform the actual removal of the widget.""" del dt try: # This widget has a container parent that must be removed self._container.parent.remove_widget(self._container) except AttributeError: pass self.on_remove_from_slide() def _convert_animation_value_to_float(self, prop: str, val: Union[str, int, float]) -> Union[float, int]: """ Convert an animation property value to a numeric value. Args: prop: The name of the property to animate val: The animation target value (may be a string that contains a % sign) Returns: Numeric value (float or int). """ try: val = percent_to_float(val, self._percent_prop_dicts[prop]) except KeyError: # because widget properties can include a % sign, they are # all strings, so even ones that aren't on the list to look # for percent signs have to be converted to numbers. if '.' in val: val = float(val) else: val = int(val) return val def build_animation_from_config(self, config_list: list) -> Animation: """Build animation object from config.""" if not isinstance(config_list, list): raise TypeError('build_animation_from_config requires a list') # find any named animations and replace them with the real ones animation_list = list() for entry in config_list: if 'named_animation' in entry: for named_anim_settings in ( self.mc.animations[entry['named_animation']]): animation_list.append(named_anim_settings) else: animation_list.append(entry) repeat = False animation_sequence_list = [] for settings in animation_list: prop_dict = dict() values_needed = dict() values = settings['value'].copy() # Some properties that can be animated contain more than single values # (such as color). Need to ensure there are the correct number of # values for the properties to animate. values_needed_total = 0 for prop in settings['property']: if isinstance(getattr(self, prop), list): values_needed[prop] = len(getattr(self, prop)) values_needed_total += values_needed[prop] else: values_needed[prop] = 1 values_needed_total += 1 if len(settings['value']) != values_needed_total: self.mc.log.warning("There is a mismatch between the number of values " "available and the number of values required to animate " "the following properties in the %s widget: %s " "(animation will be ignored).", self.widget_type_name, settings['property']) continue # Create a dictionary of properties to animate along with their target values for prop in settings['property']: # Make sure target widget property can be animated if prop not in self.animation_properties: self.mc.log.warning("%s widgets do not support animation " "for the %s property (animation will be ignored)", self.widget_type_name, prop) continue # Convert target value(s) to numeric types if values_needed[prop] > 1: val = [self._convert_animation_value_to_float(prop, x) for x in values[:values_needed[prop]]] del values[:values_needed[prop]] else: val = self._convert_animation_value_to_float(prop, values[0]) del values[0] prop_dict[prop] = val # Save the pre-animated property value so it can later be restored if prop not in self._pre_animated_settings: self._pre_animated_settings[prop] = getattr(self, prop) # TODO: Support custom easing functions # This can be done by replacing transition string with a function reference # when the string does not exist in the Kivy AnimationTransition class as # a method. # Create the animation object if settings['relative']: animation = RelativeAnimation(duration=settings['duration'], transition=settings['easing'], **prop_dict) else: animation = Animation(duration=settings['duration'], transition=settings['easing'], **prop_dict) # Determine if this animation should be performed in sequence or in parallel # with the previous animation. if settings['timing'] == 'with_previous' and animation_sequence_list: # Combine in parallel with previous animation animation_sequence_list[-1] &= animation else: # Add new sequential animation to the list animation_sequence_list.append(animation) if settings['repeat']: repeat = True # Combine all animations that should be performed in sequence into a single # animation object (add them all together) final_animation = reduce(lambda x, y: x + y, animation_sequence_list) if repeat: final_animation.repeat = True return final_animation def stop_animation(self) -> None: """Stop the current widget animation.""" try: self.animation.stop(self) except AttributeError: pass def reset_animations(self, **kwargs) -> None: """Reset the widget properties back to their pre-animated values.""" del kwargs for k, v in self._pre_animated_settings.items(): setattr(self, k, v) def _register_animation_events(self, event_name: str) -> None: """Register handlers for the various events that trigger animation actions.""" self._animation_event_keys.add(self.mc.events.add_handler( event=event_name, handler=self.start_animation_from_event, event_name=event_name)) def start_animation_from_event(self, event_name: str, **kwargs) -> None: """Starts an animation based on an event name that has previously been registered.""" del kwargs if event_name not in self.config['animations']: return self.stop_animation() self.animation = self.build_animation_from_config( self.config['animations'][event_name]) self.animation.start(self) def _remove_animation_events(self) -> None: """Remove previously registered handlers for the various events that trigger animation actions.""" self.mc.events.remove_handlers_by_keys(self._animation_event_keys) self._animation_event_keys = set() def on_add_to_slide(self, dt) -> None: """Automatically called when this widget is added to a slide. If you subclass this method, be sure to call super(), as it's needed for widget animations. """ del dt if 'add_to_slide' in self.config['reset_animations_events']: self.reset_animations() self.start_animation_from_event('add_to_slide') def on_remove_from_slide(self) -> None: """Automatically called when this widget is removed from a slide. If you subclass this method, be sure to call super(), as it's needed for widget animations. """ if 'remove_from_slide' in self.config['reset_animations_events']: self.reset_animations() def on_pre_show_slide(self) -> None: """Automatically called when the slide this widget is part of is about to be shown. If there's an entrance transition, this method is called before the transition starts. If you subclass this method, be sure to call super(), as it's needed for widget animations. """ if 'pre_show_slide' in self.config['reset_animations_events']: self.reset_animations() if 'pre_show_slide' in self.config['animations']: self.start_animation_from_event('pre_show_slide') def on_show_slide(self) -> None: """Automatically called when the slide this widget is part of has been shown. If there's an entrance transition, this method is called after the transition is complete. If you subclass this method, be sure to call super(), as it's needed for widget animations. """ if 'show_slide' in self.config['reset_animations_events']: self.reset_animations() if 'show_slide' in self.config['animations']: self.start_animation_from_event('show_slide') def on_pre_slide_leave(self) -> None: """Automatically called when the slide this widget is part of is about to leave (e.g. when another slide is going to replace it). If there's an exit transition, this method is called before the transition starts. If you subclass this method, be sure to call super(), as it's needed for widget animations. """ if 'pre_slide_leave' in self.config['reset_animations_events']: self.reset_animations() if 'pre_slide_leave' in self.config['animations']: self.start_animation_from_event('pre_slide_leave') def on_slide_leave(self) -> None: """Automatically called when the slide this widget is part of is about to leave (e.g. when another slide is going to replace it). If there's an exit transition, this method is called after the transition is complete. If you subclass this method, be sure to call super(), as it's needed for widget animations. """ if 'slide_leave' in self.config['reset_animations_events']: self.reset_animations() if 'slide_leave' in self.config['animations']: self.start_animation_from_event('slide_leave') def on_slide_play(self) -> None: """Automatically called when the slide this widget is part of is played as part of a slide_player play command (either via a standalone slide player or as a show step). If you subclass this method, be sure to call super(), as it's needed for widget animations. """ if 'slide_play' in self.config['reset_animations_events']: self.reset_animations() if 'slide_play' in self.config['animations']: self.start_animation_from_event('slide_play') def find_widgets_by_key(self, key: str) -> List["KivyWidget"]: """Return a list of widgets with the matching key value by searching the tree of children belonging to this widget.""" return [x for x in self.walk(restrict=True, loopback=False) if hasattr(x, 'key') and x.key == key] # # Properties # def _get_container(self) -> KivyWidget: return self._container container = AliasProperty(_get_container, None) '''The widget container is a special container/parent widget that manages this widget. It has no graphical representation.''' key = StringProperty(None, allownone=True) '''Widget keys are used to uniquely identify instances of widgets which you can later use to update or remove the widget. ''' color = ListProperty([1.0, 1.0, 1.0, 1.0]) '''The color of the widget, in the (r, g, b, a) format. :attr:`color` is a :class:`~kivy.properties.ListProperty` and defaults to [1.0, 1.0, 1.0, 1.0]. ''' anchor_x = StringProperty(None, allownone=True) '''Which edge of the widget will be used for positioning. ('left', 'center' (or 'middle'), or 'right'. If None, 'center' will be used. ''' anchor_y = StringProperty(None, allownone=True) '''Which edge of the widget will be used for positioning. ('top', 'middle' (or 'center'), or 'bottom'. If None, 'center' will be used. ''' anchor_pos = ReferenceListProperty(anchor_x, anchor_y) '''Which point of the widget will be used for positioning. :attr:`anchor_pos` is a :class:`~kivy.properties.ReferenceListProperty` of (:attr:`anchor_x`, :attr:`anchor_y`) properties. ''' adjust_top = NumericProperty(0) '''Moves the "top" of this widget's anchor position down, meaning any positioning that includes calculations involving the top (anchor_y of 'top' or 'middle') use the alternate top position. Positive values move the top towards the center of the widget, negative values move it away. Negative values can be used to give the widget "space" on the top, and positive values can be used to remove unwanted space from the top of the widget. Note that this setting does not actually crop or cut off the top of the widget, rather, it just adjusts how the positioning is calculated. ''' adjust_right = NumericProperty(0) '''Adjusts the anchor position calculations for the right side of the widget. Positive values move the right position towards the center, negative values move it away from the center. ''' adjust_bottom = NumericProperty(0) '''Adjusts the anchor position calculations for the bottom of the widget. Positive values move the bottom position towards the center, negative values move it away from the center. ''' adjust_left = NumericProperty(0) '''Adjusts the anchor position calculations for the left side of the widget. Positive values move the left position towards the center, negative values move it away from the center. ''' def _get_anchor_offset_pos(self): """Calculates the anchor offset position relative to the lower-left corner of a widget based on several positioning parameters. """ # Set defaults offset_x = 0 offset_y = 0 anchor_x = self.anchor_x anchor_y = self.anchor_y if not anchor_x: anchor_x = 'center' if not self.anchor_y: anchor_y = 'middle' # ---------------------- # X / width / horizontal # ---------------------- # Adjust for anchor_x & adjust_right/left if anchor_x in ('center', 'middle'): offset_x -= (self.width - self.adjust_right + self.adjust_left) / 2 elif anchor_x == 'right': offset_x -= self.width - self.adjust_right else: # left offset_x -= self.adjust_left # -------------------- # Y / height / vertical # -------------------- # Adjust for anchor_y & adjust_top/bottom if anchor_y in ('middle', 'center'): offset_y -= (self.height - self.adjust_top + self.adjust_bottom) / 2 elif anchor_y == 'top': offset_y -= self.height - self.adjust_top else: # bottom offset_y -= self.adjust_bottom return offset_x, offset_y anchor_offset_pos = AliasProperty(_get_anchor_offset_pos, None, bind=('size', 'anchor_x', 'anchor_y', 'adjust_top', 'adjust_right', 'adjust_bottom', 'adjust_left'), cache=True) '''The anchor position of the widget (relative to the widget's lower left corner).
class ListBSIconLeft(ILeftBody, MDLabel): icon = StringProperty()
class RecycleDataBox(BoxLayout): data = ListProperty() '''The data used by DataBox. This is a list of dicts whose keys map to the corresponding property names of the viewclass''' viewclass = StringProperty() '''The viewclass that will be generated from each data dict''' use_recycling = False scroller = ObjectProperty() viewclass_class = None last_height = NumericProperty() default_height = NumericProperty(100) # default hight of viewclasses max_viewclasses = 0 # scroller.height / default_height + margin indexed_widgets = False can_scroll = 1 last_scroll = 0.0 scheduled_scroll = False def __init__(self, data=None, **kwargs): super(RecycleDataBox, self).__init__(**kwargs) self._topwidget = ShellWidget(place='top') self._botwidget = ShellWidget(place='bot') self.view = BoxLayout(size_hint_y=None, orientation='vertical') self.view.bind(minimum_size=self.view.setter('size')) self.height_cache = {} for widget in self._topwidget, self.view, self._botwidget: self.add_widget(widget) def on_height(self, _, value): self.last_height = value def on_scroller(self, _, scroller): if scroller: if not self.view.children and self.data: self.on_data(None, self.data) scroller.bind(height=self.on_scroller_height) scroller.bind(scroll_y=self.on_scrolling) self.on_scroller_height(scroller, scroller.height) def on_scroller_height(self, scroller, value): self.max_viewclasses = int((value * 2) / self.default_height) def fin2(self, *args): if not self.scheduled_scroll: self.can_scroll = 1 Clock.schedule_once( lambda *a: self.on_scrolling(None, -1, sc=True), 0.1) self.scheduled_scroll = True @mainthread def unlock_scroll(self): self.can_scroll = 1 @mainthread def on_scrolling(self, scroller, scroll_y, force=False, move_one=False, sc=False): if self.scheduled_scroll: self.scheduled_scroll = False if not self.use_recycling: return if not self.can_scroll and not force: return self.unlock_scroll() winheight = Window.system_size[1] maxpos, minpos = winheight * 1.2, winheight * -0.2 center = self.view.children[int(len(self.view.children) / 2)] center_pos = center.to_window(*center.pos)[1] top = self.view.children[-1] top_pos = top.to_window(0, top.top)[1] bot = self.view.children[0] bot_pos = bot.to_window(0, bot.y)[1] if not move_one: if top_pos < minpos - winheight: self._topwidget.swap_height(top_pos - winheight) elif bot_pos > maxpos + winheight: self._botwidget.swap_height(top_pos - winheight) else: move_one = True if move_one: cnt = 0 if top_pos <= maxpos: self._topwidget.swap_one() sc = False elif bot_pos >= minpos: self._botwidget.swap_one() sc = False self.can_scroll = 0 self.unlock_scroll() if not sc: Clock.schedule_once(self.fin2, 0) def refresh_indexes(self, data): if not self._topwidget.fake_children: start_index = 0 else: start_index = self._topwidget.highest_index + 1 for i, child in enumerate(reversed(self.view.children)): i += start_index child.refresh_view_attrs(self, i, data[i]) def move_view_index(self, index): for i, child in enumerate(reversed(self.view.children)): new_index = i + index child.refresh_view_attrs(self, new_index, self.data[new_index]) def move_view_top(self): top = self.view.children[-1] bot = self.view.children[0] old_index = bot.index new_index = top.index - 1 self.height_cache[bot.index] = bot.height self.view.remove_widget(bot) self.view.add_widget(bot, index=len(self.view.children)) bot.refresh_view_attrs(self, new_index, self.data[new_index]) return new_index, old_index def move_view_bot(self): top = self.view.children[-1] bot = self.view.children[0] old_index = top.index new_index = bot.index + 1 self.height_cache[top.index] = top.height self.view.remove_widget(top) self.view.add_widget(top) top.refresh_view_attrs(self, new_index, self.data[new_index]) return new_index, old_index def on_data(self, _, data): if self.viewclass_class and self.scroller and self.max_viewclasses: data_count = len(data) # Use normal behavior when data is short if data_count <= self.max_viewclasses: self._botwidget.height = 0 self._topwidget.height = 0 self.use_recycling = False return self.on_data_no_recycle(None, data) if not self.use_recycling: self.use_recycling = True for i in range(self.max_viewclasses): instance = self.viewclass_class() self.view.add_widget(instance) children_count = self.get_children_count() if children_count != data_count: # Add if children_count < data_count: for count in range(data_count - children_count): self._botwidget.add_widget() # Remove else: counter = 0 while counter < children_count - data_count: if self._botwidget.fake_children: self._botwidget.remove_widget() else: self._topwidget.remove_widget() counter += 1 self.refresh_indexes(data) def on_data_no_recycle(self, _, data): if len(self.view.children) > self.max_viewclasses: self.clear_widgets() if self.viewclass_class: children_count = len(self.view.children) data_count = len(data) if children_count != data_count: if children_count < data_count: for count in range(data_count - children_count): self.view.add_widget(self.viewclass_class()) else: for count in range(children_count - data_count): self.view.remove_widget(self.view.children[-1]) if self.view.children: for i, child in enumerate(reversed(self.view.children)): child.refresh_view_attrs(self, i, data[i]) def get_view_count(self): return len(self.view.children) def get_children_count(self): return (len(self._topwidget.fake_children) + len(self.view.children) + len(self._botwidget.fake_children)) def refresh_from_data(self, *args): self.on_data(None, self.data) def on_viewclass(self, instance, value): if isinstance(value, string_types): self.viewclass_class = getattr(Factory, value) self.on_data(None, self.data) else: Logger.error("{}: on_viewclass: {} is not an instance".format( self, e))
class GridBSItem(ButtonBehavior, BoxLayout): source = StringProperty() caption = StringProperty()
class SyncScreen(Screen): loggedInUser = StringProperty('') localPasswordList = ListProperty([]) remotePasswordList = ListProperty([]) def syncWithRemote(self): print "sync with remote" commandData = json.dumps({"action": "SYNC", "subaction": "PULL"}) recvJsonData = self.parent.clientConnection.send_receive(commandData) self.parent.cursor.execute("delete from passwords where username='******'") pushPasswordData = recvJsonData['additional']['passwords'] for password in pushPasswordData: self.parent.cursor.execute( "insert into passwords (username, account, password) values ('" + self.loggedInUser + "', '" + password['account'] + "', '" + password['password'] + "')") self.parent.db.commit() self.loadPasswordList_UI() def syncWithLocal(self): print "sync with local" commandData = json.dumps({ "action": "SYNC", "subaction": "PUSH", "passwords": str(self.localPasswordList) }) recvJsonData = self.parent.clientConnection.send_receive(commandData) self.loadPasswordList_UI() def onPasswordButtonClick(self, instance): for button in self.ids.local_password_list.children: button.background_color = (1, 1, 1, 1) for button in self.ids.remote_password_list.children: button.background_color = (1, 1, 1, 1) instance.background_color = (0.8, 0.8, 0.8, 1) self.currentAccount_pw = str(instance.pw_account) self.currentId_pw = str(instance.pw_id) self.currentPassword_pw = str(instance.pw_password) self.ids.password_info.text = "Account: {0}\nPassword: {1}".format( self.currentAccount_pw, self.currentPassword_pw) self.ids.password_location.text = "Location: {0}".format( instance.pw_location) def readPasswords(self): commandData = json.dumps({"action": "CRUD", "subaction": "READ"}) recvJsonData = self.parent.clientConnection.send_receive(commandData) self.remotePasswordList = recvJsonData['additional']['passwords'] passwordList = PasswordRead(self.parent.db, self.loggedInUser) self.localPasswordList = passwordList def loadPasswordList_UI(self): self.readPasswords() self.ids.remote_password_list.clear_widgets() self.ids.local_password_list.clear_widgets() self.ids.remote_password_list.add_widget( Label(text="Remote Password List")) self.ids.local_password_list.add_widget( Label(text="Local Password List")) cipher = AESCipher("nv93h50sk1zh508v") for entry in self.localPasswordList: passwordBtn = PasswordButton(text=entry['account'], background_color=(0.93, 0.93, 0.93, 1)) passwordBtn.pw_username = entry['username'] passwordBtn.pw_account = entry['account'] try: passwordBtn.pw_password = cipher.decrypt(entry['password']) except Exception, e: print e passwordBtn.pw_password = "******" passwordBtn.pw_id = entry['id'] passwordBtn.pw_id = entry['id'] passwordBtn.pw_location = "Local" passwordBtn.bind(on_release=self.onPasswordButtonClick) self.ids.local_password_list.add_widget(passwordBtn) for entry in self.remotePasswordList: passwordBtn = PasswordButton(text=entry['account'], background_color=(0.93, 0.93, 0.93, 1)) passwordBtn.pw_username = entry['username'] passwordBtn.pw_account = entry['account'] try: passwordBtn.pw_password = cipher.decrypt(entry['password']) except Exception, e: print e passwordBtn.pw_password = "******" passwordBtn.pw_id = entry['id'] passwordBtn.pw_id = entry['id'] passwordBtn.pw_location = "Remote" passwordBtn.bind(on_release=self.onPasswordButtonClick) self.ids.remote_password_list.add_widget(passwordBtn)
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 PointRenderer(ScaledValues, SecondaryDataSource): color = ListProperty([1.] * 4) point_size = NumericProperty(10.0) shader = StringProperty('') #shader = OptionProperty('NO SECONDARY',['NO EFFECT']) def __init__(self, *args, **kwargs): glEnable(0x8642) # equivalend to glEnable(GL_VERTEX_PROGRAM_POINT_SIZE) self.shader_fn = 'point_renderer.glsl' super(PointRenderer, self).__init__(**kwargs) self.loadShaders() self.mesh = Mesh(mode='points', fmt=[(b'v_pos', 2, 'float'), (b'parm', 1, 'float')]) # ,pointsize=1000) self.render_context.add(self.mesh) self.updateModelViewMatrix() def registerConfigurableProperties(self): super(PointRenderer, self).registerConfigurableProperties() self.addConfigurableProperty(PointRenderer.point_size) def update(self): if self.enabled: pos_data = np.array(self.a, dtype=np.float32) pos_data = self.apply_preprocessing(pos_data) N = np.size(pos_data) / 2 pos_data = pos_data.reshape(N, 2) if self.secondary_varname != '': parm_data = np.array(self.b, dtype=np.float32).reshape(N, 1) parm_data = self.apply_secondary_preprocessing(parm_data) else: parm_data = np.ones(N, dtype=np.float32).reshape(N, 1) data = np.hstack([pos_data, parm_data]) if N > 0: self.mesh.indices = np.arange(N) # ,dtype=np.float32) self.mesh.vertices = data.ravel() # should be ravel for efficiency? self.render_context.ask_update() def setTarget(self): if self.target_object is not None and self.target_varname != '': s = 'self.a = self.target_object.%s' % (self.target_varname) exec(s) # if isinstance(self.a,np.ndarray) and len(np.shape(self.a))==2 : # pass # # self.colorfmt = ['ZERO_DEPTH_ARRAY?','luminance', # # 'luminance_alpha','rgb','rgba'][self.depth] # else : # print(np.shape(self.a),self.target_object,self.target_varname) # raise TypeError('Target of Array Renderer must be a 2D numpy.ndarray') def inspect(self): inspection_dump_file = self.createInspectionDumpFile() np.save(open(inspection_dump_file, 'wb'), self.a) self.launchInspector(inspection_dump_file) def loadShaders(self): self.shaders = loadShaders(self.shader_fn, {'point_size': 0.025 * self.point_size * min(Window.width, Window.height)}) self.render_context.shader.vs = self.shaders['vs'] self.render_context.shader.fs = self.shaders['fs'] def on_point_size(self, obj, value): self.loadShaders() def on_size(self, inst, value): super(PointRenderer, self).on_size(inst, value) self.loadShaders() def on_pos(self, inst, value): super(PointRenderer, self).on_pos(inst, value) self.loadShaders() def on_color(self, obj, value): print('on_color') self.render_context['color'] = [float(v) for v in self.color] def on_shader(self, obj, glsl_fn): self.shader_fn = glsl_fn self.loadShaders()
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 FilmThicknessScreen(Screen): """properties bound to dropdown selection""" solvs = StringProperty('') solts = StringProperty('') """properties bound to inputs""" vol = StringProperty('') conc = StringProperty('') sden = StringProperty('') mden = StringProperty('') area = StringProperty('') """properties bound to output""" thickness = StringProperty('') def calculate(self): if self.verify() == True: conc = float(self.conc) vol = float(self.vol) sden = float(self.sden) mden = float(self.mden) area = float(self.area) """computing the mass in on ml of solution""" a = (1 - conc) / (conc * sden) b = 1 / (mden) mass = vol * (a + b)**(-1) """finding the volume of the film""" film_vol = mass / mden """using area guess to estimate film thickness""" self.thickness = str(round(film_vol / area * 10000, 4)) + " Microns" else: _, message = self.verify() self.thickness = self.error_message(message) def verify(self, check_density=False): message = [] """checking concentration field""" if self.conc == '': message.append("Concentration field is empty\n") else: try: if float(self.conc) > 1: message.append("Concentration must be less than 1\n") except: message.append("Concentration must be greater than 0\n") """checking volume field""" if self.vol == '': message.append("Volume field is empty\n") else: try: if float(self.vol) < 0: message.append("Volume must be greater than 0\n") except: pass """checking density fields""" if (self.sden == ''): message.append("Solvent density field is empty\n") if (self.mden == ''): message.append("Material density field is empty\n") """Here we return messages only if False, for use with error_message()""" if message == []: return True else: return False, message def error_message(self, messages): err = 'Error:\n' for message in messages: err += message return err
class CupertinoAlertDialog(ModalView): """ iOS style Alert Dialog .. image:: ../_static/alert_dialog/demo.gif """ title = StringProperty(' ') """ Text of title of :class:`CupertinoAlertDialog` .. image:: ../_static/alert_dialog/title.png **Python** .. code-block:: python CupertinoAlertDialog(title='Hello World') **KV** .. code-block:: CupertinoAlertDialog: title: 'Hello World' """ content = StringProperty(' ') """ Text of content of :class:`CupertinoAlertDialog` .. image:: ../_static/alert_dialog/content.png **Python** .. code-block:: python CupertinoAlertDialog(content='Hello World') **KV** .. code-block:: CupertinoAlertDialog: content: 'Hello World' """ color = ColorProperty([1, 1, 1, 0.95]) """ Background color of :class:`CupertinoAlertDialog` .. image:: ../_static/alert_dialog/color.png **Python** .. code-block:: python CupertinoAlertDialog(color=(1, 0, 0, 1)) **KV** .. code-block:: CupertinoAlertDialog: color: 1, 0, 0, 1 """ color_down = ColorProperty([0.9, 0.9, 0.9, 1]) """ Background color of action of :class:`CupertinoAlertDialog` when pressed .. image:: ../_static/alert_dialog/color_down.gif **Python** .. code-block:: python CupertinoAlertDialog(color_down=(0.5, 0, 0, 1)) **KV** .. code-block:: CupertinoAlertDialog: color_down: 0.5, 0, 0, 1 """ curve = NumericProperty(15) """ Curve of :class:`CupertinoAlertDialog` .. image:: ../_static/alert_dialog/curve.png **Python** .. code-block:: python CupertinoAlertDialog(curve=20) **KV** .. code-block:: CupertinoAlertDialog: curve: 20 """ def add_action(self, text, action): """ Add an action to :class:`CupertinoAlertDialog` :param text: Text of action. ``markup`` is enabled :param action: Callback to be performed, bound to ``on_release`` of action """ self.actions.add_widget( _CupertinoDialogButton(text=text, color_normal=self.color, color_down=self.color_down, on_release=action)) if len(self.actions.children) > 2 or len(sub(r'\[.*?\]', '', text)) > 10: self.actions.orientation = 'vertical' if self.actions.orientation == 'vertical': self.size_hint_y += 0.05 self.actions.size_hint_y += 0.05 self.actions.children[0].radii[-2:] = [self.curve, self.curve] for button in self.actions.children[1:]: button.radii = [0, 0, 0, 0] else: self.actions.children[-1].radii[-2:] = (0, self.curve) self.actions.children[0].radii[2] = self.curve
class MDBanner(MDCard, FakeRectangularElevationBehavior): vertical_pad = NumericProperty(dp(68)) """ Indent the banner at the top of the screen. :attr:`vertical_pad` is an :class:`~kivy.properties.NumericProperty` and defaults to `dp(68)`. """ opening_transition = StringProperty("in_quad") """ The name of the animation transition. :attr:`opening_transition` is an :class:`~kivy.properties.StringProperty` and defaults to `'in_quad'`. """ icon = StringProperty("data/logo/kivy-icon-128.png") """ Icon banner. :attr:`icon` is an :class:`~kivy.properties.StringProperty` and defaults to `'data/logo/kivy-icon-128.png'`. """ over_widget = ObjectProperty() """ The widget that is under the banner. It will be shifted down to the height of the banner. :attr:`over_widget` is an :class:`~kivy.properties.ObjectProperty` and defaults to `None`. """ text = ListProperty() """ List of lines for banner text. Must contain no more than three lines for a `'one-line'`, `'two-line'` and `'three-line'` banner, respectively. :attr:`text` is an :class:`~kivy.properties.ListProperty` and defaults to `[]`. """ left_action = ListProperty() """ The action of banner. To add one action, make a list [`'name_action'`, callback] where `'name_action'` is a string that corresponds to an action name and ``callback`` is the function called on a touch release event. :attr:`left_action` is an :class:`~kivy.properties.ListProperty` and defaults to `[]`. """ right_action = ListProperty() """ Works the same way as :attr:`left_action`. :attr:`right_action` is an :class:`~kivy.properties.ListProperty` and defaults to `[]`. """ type = OptionProperty( "one-line", options=[ "one-line", "two-line", "three-line", "one-line-icon", "two-line-icon", "three-line-icon", ], allownone=True, ) """ Banner type. . Available options are: (`"one-line"`, `"two-line"`, `"three-line"`, `"one-line-icon"`, `"two-line-icon"`, `"three-line-icon"`). :attr:`type` is an :class:`~kivy.properties.OptionProperty` and defaults to `'one-line'`. """ opening_timeout = BoundedNumericProperty(0.7, min=0.7) """ Time interval after which the banner will be shown. .. versionadded:: 1.0.0 :attr:`opening_timeout` is an :class:`~kivy.properties.BoundedNumericProperty` and defaults to `0.7`. """ opening_time = NumericProperty(0.15) """ The time taken for the banner to slide to the :attr:`state` `'open'`. .. versionadded:: 1.0.0 :attr:`opening_time` is a :class:`~kivy.properties.NumericProperty` and defaults to `0.15`. """ closing_time = NumericProperty(0.15) """ The time taken for the banner to slide to the :attr:`state` `'close'`. .. versionadded:: 1.0.0 :attr:`closing_time` is a :class:`~kivy.properties.NumericProperty` and defaults to `0.15`. """ _type_message = None _progress = False def add_actions_buttons(self, instance_box: MDBoxLayout, data: list) -> NoReturn: """ Adds buttons to the banner. :param data: ['NAME BUTTON', <function>]; """ if data: name_action_button, function_action_button = data action_button = MDFlatButton( text=f"[b]{name_action_button}[/b]", theme_text_color="Custom", text_color=self.theme_cls.primary_color, on_release=function_action_button, ) action_button.markup = True instance_box.add_widget(action_button) def show(self) -> NoReturn: """Displays a banner on the screen.""" def show(interval: Union[int, float]): self.set_type_banner() self.add_actions_buttons(self.ids.left_action_box, self.left_action) self.add_actions_buttons(self.ids.right_action_box, self.right_action) self._add_banner_to_container() Clock.schedule_once(self.animation_display_banner, 0.1) if not self._progress: self._progress = True if self.ids.container_message.children: self.hide() Clock.schedule_once(show, self.opening_timeout) def hide(self) -> NoReturn: """Hides the banner from the screen.""" def hide(interval: Union[int, float]): anim = Animation(banner_y=0, d=self.closing_time) anim.bind(on_complete=self._remove_banner) anim.start(self) Animation(y=self.over_widget.y + self.height, d=self.closing_time).start(self.over_widget) if not self._progress: self._progress = True Clock.schedule_once(hide, 0.5) def set_type_banner(self) -> NoReturn: self._type_message = { "three-line-icon": ThreeLineIconBanner, "two-line-icon": TwoLineIconBanner, "one-line-icon": OneLineIconBanner, "three-line": ThreeLineBanner, "two-line": TwoLineBanner, "one-line": OneLineBanner, }[self.type] def animation_display_banner(self, interval: Union[int, float]) -> NoReturn: Animation( banner_y=self.height + self.vertical_pad, d=self.opening_time, t=self.opening_transition, ).start(self) anim = Animation( y=self.over_widget.y - self.height, d=self.opening_time, t=self.opening_transition, ) anim.bind(on_complete=self._reset_progress) anim.start(self.over_widget) def _remove_banner(self, *args): self.ids.container_message.clear_widgets() self.ids.left_action_box.clear_widgets() self.ids.right_action_box.clear_widgets() self._reset_progress() def _reset_progress(self, *args): self._progress = False def _add_banner_to_container(self) -> NoReturn: self.ids.container_message.add_widget( self._type_message(text_message=self.text, icon=self.icon))
class StackPart(ButtonBehavior, BoxLayout): selected = BooleanProperty(False) row = NumericProperty(0) template = StringProperty() tmplWidget = ObjectProperty() name = StringProperty() values = DictProperty() source = StringProperty() image = ObjectProperty(False) layout = ObjectProperty(None) def realise(self, withValue=False, use_cache=False): Logger.info('Calling realisze on %s (with Value %s / use_cache %s' % (self, withValue, use_cache)) #Force the creation of an image from self.template, thourhg real display #Skipt of computed image exists if self.image: return from kivy.clock import Clock #Force the creaiotn of the tmpl miniture for display from template import BGTemplate if not self.template: return try: if not use_cache: Logger.info('[SGM]Realize StackPart calling from file') tmpl = BGTemplate.FromFile(self.template, use_cache)[-1] except IndexError: Logger.warn('Warning: template file %s contains no Template !!' % self.template) from utils import alert alert('Error while loading template %s ' % self.template) return #App.get_running_app().root.ids['realizer'].add_widget(tmpl) #force draw of the beast if withValue: tmpl.apply_values(self.values) self.tmplWidget = tmpl def inner(*args): #Here is hould loop on the template to apply them on values from kivy.base import EventLoop EventLoop.idle() cim = tmpl.toImage() cim.texture.flip_vertical() self.ids['img'].texture = cim.texture #App.get_running_app().root.ids['realizer'].remove_widget(tmpl) Clock.schedule_once(inner, -1) def on_press(self): self.selected = not (self.selected) if self.selected: #update on dad selection if self.parent.last_selected and self.parent.last_selected != self: self.parent.last_selected.selected = False self.parent.last_selected = self def on_selected(self, instance, selected): if self.template: self.realise(True) if self.selected: from kivy.uix.boxlayout import BoxLayout BOX = BoxLayout(size_hint=(None, None), orientation='vertical', spacing=10) W = 90 #Add Remove Button b = Factory.get('HiddenRemoveButton')(icon='trash', width=W) b.bind(on_press=lambda x: self.parent.remove_widget(self)) #self.add_widget(b) be = Factory.get('HiddenRemoveButton')(icon='edit', width=W) if self.template: #it is a template: add edit & export buttons def inner(*args): p = Factory.get('TemplateEditPopup')() p.name = self.template options = p.ids['options'] #print 'editing options with ', self.values options.values = self.values options.tmplPath = self.template #trigger options building on popup p.stackpart = self p.open() p.do_layout() p.content.do_layout() from kivy.clock import Clock def _inner(*args): prev = p.ids['preview'] tmpl = prev.children[0] TS = tmpl.size PS = prev.parent.size W_ratio = float(.9 * PS[0]) / TS[0] H_ratio = float(.9 * PS[1]) / TS[1] ratio = min(W_ratio, H_ratio) x, y = p.ids['FL'].center prev.center = ratio * x, ratio * y #p.ids['preview'].scale = ratio #forcing x to 0 prev.x = 5 prev.center_y = y from kivy.metrics import cm p.tsize = options.current_selection[0].size[0] / cm( 1), options.current_selection[0].size[1] / cm(1) #print prev, prev.size, prev.pos #print 'ratio', ratio #print prev.parent, prev.parent.size, prev.parent.pos #print prev.parent.parent Clock.schedule_once(_inner, 0) else: #edit button for pure image def inner(*args): p = Factory.get('SizeEditPopup')() p.name = self.source _img = self.toPILImage() p.ids.width.text = str(_img.size[0]) p.ids.height.text = str(_img.size[1]) p.open() def _inner(*args): w, h = float(p.ids.width.text), float( p.ids.height.text) if p.ids.w_metric.text == "cm": from kivy.metrics import cm w *= cm(1) if p.ids.h_metric.text == "cm": from kivy.metrics import cm h *= cm(1) self.setImageSize((w, h)) p.cb = _inner be.bind(on_press=inner) #self.add_widget(be) BOX.add_widget(be) #Img Export button bx = Factory.get('HiddenRemoveButton')(icon='export', width=W) bx.bind(on_press=lambda x: self.img_export()) BOX.add_widget(bx) BOX.add_widget(b) self.add_widget(BOX) #Now Also Add a second box, with Up & Down !!! BOX = BoxLayout(size_hint=(None, None), orientation='vertical', spacing=0, width=30) bx = Factory.get('HiddenRemoveButton')(icon='up-big') bx.bind(on_press=lambda x: self.parent.promote(self)) BOX.add_widget(bx) bx = Factory.get('HiddenRemoveButton')(icon='down-big') bx.bind(on_press=lambda x: self.parent.demote(self)) BOX.add_widget(bx) self.add_widget(BOX) else: self.remove_widget(self.children[0]) self.remove_widget(self.children[0]) #if self.template: # self.remove_widget(self.children[0]) self.focus = self.selected def Copy(self): blank = StackPart() for attr in [ 'template', 'name', 'values', "qt", "verso", 'source', 'image' ]: setattr(blank, attr, getattr(self, attr)) blank.ids['img'].texture = self.ids['img'].texture return blank def img_export(self, dst='export.png'): pim = self.toPILImage() pim.save(dst) from utils import start_file start_file(dst) def setImageSize(self, size): size = [int(x) for x in list(size)] img = self.toPILImage() self.setImage(img.resize(size)) def toPILImage(self): item = self if item.image: return item.image elif item.template: from PIL.Image import frombuffer if item.tmplWidget: #it has been modified tmplWidget = item.tmplWidget else: from template import BGTemplate Logger.info('[SGM] toPILIMage calling fromfile ') tmplWidget = BGTemplate.FromFile(item.template) if tmplWidget: #only taking the last one tmplWidget = tmplWidget[-1] else: raise NameError('No such template: ' + item.template) print 'here to be added: adding on realizer, exporting & then removing. more tricky' if item.values: tmplWidget.apply_values(item.values) cim = tmplWidget.toImage(for_print=True) pim = frombuffer('RGBA', cim.size, cim._texture.pixels, 'raw', 'RGBA', 0, 1) else: from PIL import Image pim = Image.open(self.source) return pim def setImage(self, pilimage): from PIL.Image import FLIP_TOP_BOTTOM from kivy.graphics.texture import Texture #Standard mode: flip the flip = pilimage.transpose(FLIP_TOP_BOTTOM) from img_xfos import img_modes ktext = Texture.create(size=flip.size) ktext.blit_buffer(flip.tobytes(), colorfmt=img_modes[flip.mode]) self.ids['img'].texture = ktext self.image = pilimage def getSize(self): from conf import FORCE_FIT_FORMAT, card_format if self.image: #stack part with computed image return self.image.size elif self.tmplWidget: return self.tmplWidget.size elif self.template: self.realise(withValue=True, use_cache=True) return self.tmplWidget.size elif FORCE_FIT_FORMAT: return card_format.size else: if self.ids.img.texture: return self.ids.img.texture.size return card_format.size
def test_stringcheck(self): from kivy.properties import StringProperty a = StringProperty() a.link(wid, 'a') a.link_deps(wid, 'a') self.assertEqual(a.get(wid), '') a.set(wid, 'hello') self.assertEqual(a.get(wid), 'hello') try: a.set(wid, 88) # number shouldn't be accepted self.fail('string accept number, fail.') except ValueError: pass
class FileChooser(FileChooserController): '''Implementation of a :class:`FileChooserController` which supports switching between multiple, synced layout views. The FileChooser can be used as follows: .. code-block:: kv BoxLayout: orientation: 'vertical' BoxLayout: size_hint_y: None height: sp(52) Button: text: 'Icon View' on_press: fc.view_mode = 'icon' Button: text: 'List View' on_press: fc.view_mode = 'list' FileChooser: id: fc FileChooserIconLayout FileChooserListLayout .. versionadded:: 1.9.0 ''' manager = ObjectProperty() ''' Reference to the :class:`~kivy.uix.screenmanager.ScreenManager` instance. manager is an :class:`~kivy.properties.ObjectProperty`. ''' _view_list = ListProperty() def get_view_list(self): return self._view_list view_list = AliasProperty(get_view_list, bind=('_view_list', )) ''' List of views added to this FileChooser. view_list is an :class:`~kivy.properties.AliasProperty` of type :class:`list`. ''' _view_mode = StringProperty() def get_view_mode(self): return self._view_mode def set_view_mode(self, mode): if mode not in self._view_list: raise ValueError('unknown view mode %r' % mode) self._view_mode = mode view_mode = AliasProperty(get_view_mode, set_view_mode, bind=('_view_mode', )) ''' Current layout view mode. view_mode is an :class:`~kivy.properties.AliasProperty` of type :class:`str`. ''' @property def _views(self): return [screen.children[0] for screen in self.manager.screens] def __init__(self, **kwargs): super(FileChooser, self).__init__(**kwargs) self.manager = ScreenManager() super(FileChooser, self).add_widget(self.manager) self.trigger_update_view = Clock.create_trigger(self.update_view) self.fbind('view_mode', self.trigger_update_view) def add_widget(self, widget, **kwargs): if widget is self._progress: super(FileChooser, self).add_widget(widget, **kwargs) elif hasattr(widget, 'VIEWNAME'): name = widget.VIEWNAME + 'view' screen = Screen(name=name) widget.controller = self screen.add_widget(widget) self.manager.add_widget(screen) self.trigger_update_view() else: raise ValueError('widget must be a FileChooserLayout,' ' not %s' % type(widget).__name__) def rebuild_views(self): views = [view.VIEWNAME for view in self._views] if views != self._view_list: self._view_list = views if self._view_mode not in self._view_list: self._view_mode = self._view_list[0] self._trigger_update() def update_view(self, *args): self.rebuild_views() sm = self.manager viewlist = self._view_list view = self.view_mode current = sm.current[:-4] viewindex = viewlist.index(view) if view in viewlist else 0 currentindex = viewlist.index(current) if current in viewlist else 0 direction = 'left' if currentindex < viewindex else 'right' sm.transition.direction = direction sm.current = view + 'view' def _create_entry_widget(self, ctx): return [ Builder.template(view._ENTRY_TEMPLATE, **ctx) for view in self._views ] def _get_file_paths(self, items): if self._views: return [file[0].path for file in items] return [] def _update_item_selection(self, *args): for viewitem in self._items: selected = viewitem[0].path in self.selection for item in viewitem: item.selected = selected def on_entry_added(self, node, parent=None): for index, view in enumerate(self._views): view.dispatch('on_entry_added', node[index], parent[index] if parent else None) def on_entries_cleared(self): for view in self._views: view.dispatch('on_entries_cleared') def on_subentry_to_entry(self, subentry, entry): for index, view in enumerate(self._views): view.dispatch('on_subentry_to_entry', subentry[index], entry) def on_remove_subentry(self, subentry, entry): for index, view in enumerate(self._views): view.dispatch('on_remove_subentry', subentry[index], entry) def on_submit(self, selected, touch=None): view_mode = self.view_mode for view in self._views: if view_mode == view.VIEWNAME: view.dispatch('on_submit', selected, touch) return