def __init__(self, engine): self.image = None self.mode = None # {None, "filedata", "mood", "gallery", "send"} self.mode_cleanup = None self.screen = None self.logo_screen = None self.list_screen = None self.gallery_screen = None self.screen_list = [] self.engine = engine self.engine.card.cb = self._card_cb self.lock = e32.Ao_lock() self.main_title = u"Tpytwink" self.old_title = appuifw.app.title self.egg_zero() appuifw.app.title = self.main_title appuifw.app.exit_key_handler = self.abort self.set_menu_items() self.set_up_canvas() self.image = graphics.Image.new(self.canvas.size) # This screen does not reflect what is in the model. It is # displayed when we want it to be, and displays whatever # situation aware wait text we want it to display. self.logo_screen = LogoScreen(self.image, self.redraw_cb) # This screen directly reflects the model, and can also be # used to edit the model. self.list_screen = ListScreen(self.image, self.redraw_cb) self.list_screen.set_mood_action(self._mood_select) self.list_screen.set_recipient_action(self._recipient_select) self.list_screen.set_picfile_action(self._picfile_select) self.gallery_screen = GalleryScreen(self.image, self.redraw_cb) self.gallery_screen.cb = self._gallery_done self.screen_list = [self.logo_screen, self.list_screen, self.gallery_screen] self._card_cb("all")
class App: """ This is the controller that owns the engine and chooses which view should be displayed and when. """ egg_list = [57, 53, 49] # keys 9, 5, 1 def __init__(self, engine): self.image = None self.mode = None # {None, "filedata", "mood", "gallery", "send"} self.mode_cleanup = None self.screen = None self.logo_screen = None self.list_screen = None self.gallery_screen = None self.screen_list = [] self.engine = engine self.engine.card.cb = self._card_cb self.lock = e32.Ao_lock() self.main_title = u"Tpytwink" self.old_title = appuifw.app.title self.egg_zero() appuifw.app.title = self.main_title appuifw.app.exit_key_handler = self.abort self.set_menu_items() self.set_up_canvas() self.image = graphics.Image.new(self.canvas.size) # This screen does not reflect what is in the model. It is # displayed when we want it to be, and displays whatever # situation aware wait text we want it to display. self.logo_screen = LogoScreen(self.image, self.redraw_cb) # This screen directly reflects the model, and can also be # used to edit the model. self.list_screen = ListScreen(self.image, self.redraw_cb) self.list_screen.set_mood_action(self._mood_select) self.list_screen.set_recipient_action(self._recipient_select) self.list_screen.set_picfile_action(self._picfile_select) self.gallery_screen = GalleryScreen(self.image, self.redraw_cb) self.gallery_screen.cb = self._gallery_done self.screen_list = [self.logo_screen, self.list_screen, self.gallery_screen] self._card_cb("all") # -------------------------------------------------------------------- # canvas def set_up_canvas(self): # We get cyclic references with these arguments. self.canvas = None self.canvas = appuifw.Canvas(self.redraw_cb, self.event_cb, self.resize_cb) appuifw.app.body = self.canvas def clear_canvas(self): appuifw.app.body = self.canvas = None def without_canvas(self, f): """ The interaction between a Canvas and an "abnormal" sized screen appears to mess up dialogs somewhat; using this is a workaround. """ self.clear_canvas() appuifw.app.screen = 'normal' try: f() finally: appuifw.app.screen = 'large' self.set_up_canvas() # -------------------------------------------------------------------- # mood mode actions def _mood_select(self): new_value = appuifw.query(u"Status:", "text", self.engine.card.mood) if new_value is not None: self.engine.card.set_mood(new_value) def _recipient_select(self): strings = [u"Make public", u"Keep private"] actions = [self.engine.set_public_recipient, self.engine.set_private_recipient] choose_execute_action(strings, actions) def _picfile_select(self): strings = [u"Choose from Gallery", u"Take new photo", u"Remove picture"] actions = [self.to_gallery_mode, self.engine.take_photo, self.engine.card.remove_picfile] if self.engine.card.picfile is not None: strings = [u"View picture", u"Rename picture"] + strings actions = [self.engine.show_picfile, self.engine.rename_picfile] + actions choose_execute_action(strings, actions) # -------------------------------------------------------------------- # gallery mode actions def _gallery_done(self, picfile): """ picfile:: None if nothing selected. """ if picfile is not None: self.engine.card.set_picfile(picfile.pn) self.to_mood_mode() # -------------------------------------------------------------------- # menu actions def send_card_cond(self): if self.engine.is_ready_to_send(): if self.engine.config.get_store_on(): strings = [u"Store card now", u"Do not store yet"] else: strings = [u"Send card now", u"Do not send yet"] actions = [self.to_send_mode, lambda: None] choose_execute_action(strings, actions) def edit_recipient(self): self.without_canvas(self.engine.edit_recipient) def toggle_debug_mode(self): self.engine.toggle_debug_on() self.set_menu_items() # -------------------------------------------------------------------- # sending def _send_status(self, tp, text): if tp == "progress": self.logo_screen.text = text self.logo_screen.redraw_text() elif tp == "ok": appuifw.note(text, "info") self.engine.card.clear_temporary() self.to_filedata_mode() else: # fail appuifw.note(text, "error") self.engine.card.clear_temporary() self.to_filedata_mode() def _card_cb(self, field): # Here we must update the relevant properties of all the # screens that may be affected, if they do already exist. # However, only any visible screen may be redrawn. if self.list_screen is not None: if field == "mood" or field == "all": self.list_screen.set_mood(self.engine.card.mood) if field == "recipient" or field == "all": self.list_screen.set_recipient(self.engine.card.recipient) if field == "picfile" or field == "all": self.list_screen.set_picfile(self.engine.card.picfile) def clear_ap(self): self.engine.config.set_apid(False) appuifw.note(u"AP cleared", "info") # -------------------------------------------------------------------- # scanning def set_gps_scan(self, value): is_on = self.engine.get_gps_scan() if is_on != value: self.engine.set_gps_scan(value) self.set_menu_items() def set_btprox_scan(self, value): is_on = self.engine.config.get_btprox_scan() if is_on != value: self.engine.config.set_btprox_scan(value) self.set_menu_items() def set_store_on(self, value): is_on = self.engine.config.get_store_on() if is_on != value: self.engine.config.set_store_on(value) self.set_menu_items() # -------------------------------------------------------------------- # external commands def ensure_have_valid_sender(self): """ Sender info is required, so failure is not an option here. """ if self.engine.card.has_valid_sender(): return def f(): ch = appuifw.selection_list([u"Specify sender manually", u"Choose from Contacts"]) if ch == 1: self.engine.select_sender() elif ch == 0: self.engine.edit_sender() if not self.engine.card.has_valid_sender(): raise "sender must be set up to use this application" self.without_canvas(f) # -------------------------------------------------------------------- # mode switching def to_mode(self, mode): if mode == self.mode: return if mode == "filedata" or mode == "send": screen = self.logo_screen elif mode == "mood": screen = self.list_screen elif mode == "gallery": screen = self.gallery_screen else: raise "assertion failure" if self.mode: if self.mode_cleanup: self.mode_cleanup() self.mode_cleanup = None self.mode = mode self.to_screen(screen) self.set_menu_items() def to_screen(self, screen): if screen is not self.screen: if self.screen is not None: self.screen.disactivate() self.screen = screen if self.screen is not None: self.screen.activate() def set_menu_items(self): main_menu = [] if self.mode == "filedata": main_menu.append((u"Edit status", self.to_mood_mode)) if self.mode == "filedata" or self.mode == "mood": if self.engine.config.get_store_on(): main_menu.append((u"Store without file data", self.send_card_cond)) else: main_menu.append((u"Send without file data", self.send_card_cond)) if self.engine.get_gps_scan(): toggle_gps_scan = (u"GPS scanning off", lambda: self.set_gps_scan(False)) else: toggle_gps_scan = (u"GPS scanning on", lambda: self.set_gps_scan(True)) if self.engine.config.get_btprox_scan(): toggle_btprox_scan = (u"BT scanning off", lambda: self.set_btprox_scan(False)) else: toggle_btprox_scan = (u"BT scanning on", lambda: self.set_btprox_scan(True)) if self.engine.config.get_store_on(): toggle_store_on = (u"Card sending on", lambda: self.set_store_on(False)) else: toggle_store_on = (u"Card storing on", lambda: self.set_store_on(True)) main_menu.extend([ (u"View GPS", lambda: self.without_canvas(self.engine.show_gps)), (u"GPS config", self.engine.edit_gps_config), toggle_gps_scan, toggle_btprox_scan, (u"Change access point", self.engine.config.change_apid), toggle_store_on ]) if self.engine.get_debug_on(): main_menu.extend([ (u"View GSM", lambda: self.without_canvas(self.engine.show_gsm)), (u"View BT", lambda: self.without_canvas(self.engine.show_btprox)), (u"Logo screen", lambda: self.to_screen(self.logo_screen)), (u"List screen", lambda: self.to_screen(self.list_screen)), (u"Gallery screen", lambda: self.to_screen(self.gallery_screen)), (u"View log", self.engine.show_log_file), (u"Choose recipient", lambda: self.without_canvas(self.engine.select_recipient)), (u"Edit recipient", self.edit_recipient), (u"Remove recipient", self.engine.remove_recipient), (u"Choose sender", lambda: self.without_canvas(self.engine.select_sender)), (u"Edit sender", lambda: self.without_canvas(self.engine.edit_sender)), (u"Remove sender", self.engine.remove_sender), (u"Remove context", self.engine.card.clear_context), (u"Remove access point", self.clear_ap), (u"Toggle scanning", self.engine.config.toggle_noscan), (u"Select Camera UID", self.engine.select_camera_uid) ]) main_menu.append((u"Exit Tpytwink", self.abort)) appuifw.app.menu = main_menu def to_filedata_mode(self): """ To avoid surprises to the user, we only acquire when the acquisition screen is showing, and not at other times. """ self.logo_screen.text = u"Waiting for file data" self.logo_screen.select_action = self.to_mood_mode self.to_mode("filedata") self.mode_cleanup = self.engine.stop_observing_filedata self.engine.start_observing_filedata(self._filedata_done) def to_mood_mode(self): self.to_mode("mood") ut.set_right_softkey_text(u"Back") def cleanup(): appuifw.app.exit_key_handler = self.abort ut.set_right_softkey_text(u"Exit") def done(): self.acquire_new_filedata() self.list_screen.disactivate_cb = cleanup appuifw.app.exit_key_handler = done self.mode_cleanup = cleanup def to_gallery_mode(self): self.to_mode("gallery") appuifw.app.exit_key_handler = lambda: self._gallery_done(None) ut.set_right_softkey_text(u"Close") def cleanup(): ut.set_right_softkey_text(u"Exit") self.mode_cleanup = cleanup def to_send_mode(self): self.logo_screen.text = u"" self.logo_screen.select_action = None self.to_mode("send") self.engine.send_card(self._send_status) # -------------------------------------------------------------------- # filedata acquisition def acquire_new_filedata(self): self.engine.card.remove_filedata() self.to_filedata_mode() def _filedata_done(self): """ Called when got the filedata. """ if self.engine.is_ready_to_send(): self.to_send_mode() else: self.acquire_new_filedata() # -------------------------------------------------------------------- # general screen callbacks def redraw_cb(self, area): """ The argument is a tuple of the form (tlx, tly, brx, bry), giving the area to redraw. """ #print repr(["REDRAW", area]) if self.canvas is None: return if self.image is None: self.canvas.rectangle(area, fill = 0xffffff) return tgtoff = (area[0], area[1]) #print "target %s source %s" % (repr(tgtoff), repr(area)) # Note that the docs are wrong, actually target = (tlx,tly), # and source = (tlx,tly,brx,bry). self.canvas.blit(image = self.image, target = tgtoff, source = area) def event_cb(self, event): #print repr(["EVENT", event]) if event["type"] == appuifw.EEventKey: code = event["keycode"] egg_code = self.egg_input.pop() if code == egg_code: if len(self.egg_input) == 0: self.toggle_debug_mode() self.egg_zero() return else: self.egg_zero() if self.screen is not None: ut.allow_attr_error(lambda: self.screen.event_cb(event)) def egg_zero(self): self.egg_input = ([] + self.egg_list) self.egg_input.reverse() def resize_cb(self, sz): """ We get one of these events at startup, and one whenever the canvas is resized. And maybe some spurious ones as well. """ #print repr(["RESIZE", sz]) if self.canvas: self.image = graphics.Image.new(self.canvas.size) for screen in self.screen_list: screen.resize(self.image, self.screen == screen) # -------------------------------------------------------------------- def abort(self): self.lock.signal() def loop(self): self.lock.wait() def close(self): self.screen = None self.logo_screen.close() self.list_screen.close() self.gallery_screen.close() # Kills the canvas (which may be somehow visible even when not # set as app.body, it seems) if this is the last reference. # Canvas still has references to self, but not the other way # around any longer, so it should get freed. self.canvas = None if not self.engine.get_debug_on(): # The effect is not immediate, but rather concerns the # point where execution falls back to the framework. appuifw.app.set_exit() appuifw.app.menu = [] appuifw.app.body = None appuifw.app.exit_key_handler = None appuifw.app.title = self.old_title