class GridWorldEnv(gym.Env): metadata = { 'render.modes': ['human'] } def __init__(self): self.width = 16 self.height = 9 self._cell_size = 10 self.action_space = spaces.Discrete(4) self.observation_space = spaces.Box(self.height * self._cell_size, self.width * self._cell_size, 1) self.viewer = Viewer(width=self.width, height=self.height, cell_size=self._cell_size) self._seed() self.reset() def _seed(self, seed=None): self.np_random, seed = seeding.np_random(seed) return [seed] def _step(self, action): assert self.action_space.contains(action), "%r (%s) invalid" % (action, type(action)) self.viewer.move_agent(action) self.state = self.viewer.get_state() done = self.viewer.is_on_goal() reward = 1 if done else 0 return self.state, reward, done, {} def _reset(self): self.viewer.reset_agent() self.state = self.viewer.get_state() return self.state def _render(self, mode='human', close=False): if close: if self.viewer is not None: self.viewer.close() self.viewer = None return return self.viewer.render() def set_grid_size(self, width, height): self.width = width self.height = height self.viewer = Viewer(height=self.height, width=self.width, cell_size=self._cell_size) self.reset()
class ContainedText(AttrElem): """Base class for a text widget contained as a cell in a canvas. Both Captions and Cells are derived from this class. """ def __init__(self, table, parentviewer, attrs): AttrElem.__init__(self, attrs) self._table = table self._container = table.container ## from profile import Profile ## from pstats import Stats ## p = Profile() ## # can't use runcall because that doesn't return the results ## p.runctx('self._viewer = Viewer(master=table.container, context=parentviewer.context, scrolling=0, stylesheet=parentviewer.stylesheet, parent=parentviewer)', ## globals(), locals()) ## Stats(p).strip_dirs().sort_stats('time').print_stats(5) self._viewer = Viewer(master=table.container, context=parentviewer.context, scrolling=0, stylesheet=parentviewer.stylesheet, parent=parentviewer) if not parentviewer.find_parentviewer(): self._viewer.RULE_WIDTH_MAGIC = self._viewer.RULE_WIDTH_MAGIC - 6 # for callback notification self._fw = self._viewer.frame self._tw = self._viewer.text self._tw.config(highlightthickness=0) self._width = 0 self._embedheight = 0 def new_formatter(self): formatter = AbstractFormatter(self._viewer) # set parskip to prevent blank line at top of cell if the content # starts with a <P> or header element. formatter.parskip = 1 return formatter def freeze(self): self._viewer.freeze() def unfreeze(self): self._viewer.unfreeze() def close(self): self._viewer.close() def maxwidth(self): return self._maxwidth # not useful until after finish() def minwidth(self): return self._minwidth # likewise def height(self): return max(self._embedheight, _get_height(self._tw)) def recalc(self): # recalculate width and height upon notification of completion # of all context's readers (usually image readers) min_nonaligned = self._minwidth maxwidth = self._maxwidth embedheight = self._embedheight # take into account all embedded windows for sub in self._viewer.subwindows: # the standard interface is used if the object has a # table_geometry() method if hasattr(sub, 'table_geometry'): submin, submax, height = sub.table_geometry() min_nonaligned = max(min_nonaligned, submin) maxwidth = max(maxwidth, submax) embedheight = max(embedheight, height) else: # this is the best we can do ## print 'non-conformant embedded window:', sub.__class__ ## print 'using generic method, which may be incorrect' geom = sub.winfo_geometry() match = CELLGEOM_RE.search(geom) if match: [w, h, x, y] = map(grailutil.conv_integer, match.group(1, 2, 3, 4)) min_nonaligned = max(min_nonaligned, w) # x+w? maxwidth = max(maxwidth, w) # x+w? embedheight = max(embedheight, h) # y+h? self._embedheight = embedheight self._minwidth = min_nonaligned self._maxwidth = maxwidth return len(self._viewer.subwindows) def finish(self, padding=0): # TBD: if self.layout == AUTOLAYOUT??? self._x = self._y = 0 fw = self._fw tw = self._tw # Set the padding before grabbing the width, but it could be # denoted as a percentage of the viewer width if type(padding) == StringType: try: # divide by 200 since padding is a percentage and we # want to put equal amounts of pad on both sides of # the picture. padding = int(self._table.get_available_width() * string.atoi(padding[:-1]) / 200) except ValueError: padding = 0 tw['padx'] = padding # TBD: according to the W3C table spec, minwidth should really # be max(min_left + min_right, min_nonaligned). Also note # that minwidth is recalculated by minwidth() call self._minwidth, self._maxwidth = _get_widths(self._tw) # first approximation of height. this is the best we can do # without forcing an update_idletasks() fireworks display tw['height'] = _get_linecount(tw) + 1 # initially place the cell in the canvas at position (0,0), # with the maximum width and closest approximation height. # situate() will be called later with the final layout # parameters. self._tag = self._container.create_window( 0, 0, window=fw, anchor=NW, width=self._maxwidth, height=fw['height']) def situate(self, x=0, y=0, width=None, height=None): # canvas.move() deals in relative positioning, but we want # absolute coordinates xdelta = x - self._x ydelta = y - self._y self._x = x self._y = y self._container.move(self._tag, xdelta, ydelta) if width <> None and height <> None: self._container.itemconfigure(self._tag, width=width, height=height) elif width <> None: self._container.itemconfigure(self._tag, width=width) else: self._container.itemconfigure(self._tag, height=height)
class Browser: """The Browser class provides the top-level GUI. It is a blatant rip-off of Mosaic's look and feel, with menus, a stop button, a URL display/entry area, and (last but not least) a viewer area. But then, so are all other web browsers. :-) """ def __init__(self, master, app=None, width=None, height=None, geometry=None): self.master = master if not app: app = grailutil.get_grailapp() prefs = app.prefs self.app = app if not width: width = prefs.GetInt('browser', 'default-width') if not height: height = prefs.GetInt('browser', 'default-height') self.create_widgets(width=width, height=height, geometry=geometry) self.root.iconname('Grail') app.add_browser(self) def create_widgets(self, width, height, geometry): # I'd like to be able to set the widget name here, but I'm not # sure what the correct thing to do is. Setting it to `grail' # is definitely *not* the right thing to do since this causes # all sorts of problems. self.root = tktools.make_toplevel(self.master, class_='Grail') self._window_title("Grail: New Browser") if geometry: self.root.geometry(geometry) self.root.protocol("WM_DELETE_WINDOW", self.on_delete) self.topframe = Frame(self.root) self.topframe.pack(fill=X) self.create_logo() self.create_menubar() self.create_urlbar() self.create_statusbar() self.viewer = Viewer(self.root, browser=self, width=width, height=height) self.context = self.viewer.context if self.app.prefs.GetBoolean('browser', 'show-logo'): self.logo_init() def create_logo(self): self.logo = Button(self.root, name="logo", command=self.stop_command, state=DISABLED) self.logo.pack(side=LEFT, fill=BOTH, padx=10, pady=10, in_=self.topframe) self.root.bind("<Alt-period>", self.stop_command) self.logo_animate = 0 def create_menubar(self): # Create menu bar, menus, and menu entries # Create menu bar self.mbar = Menu(self.root, name="menubar", tearoff=0) self.root.config(menu=self.mbar) # Create the menus self.create_menu("file") self.create_menu("go") self.histmenu = self.gomenu # backward compatibility for Ping self.create_menu("search") self.create_menu("bookmarks") self.create_menu("preferences") # List of user menus (reset on page load) self.user_menus = [] if self.get_helpspec(): self.create_menu("help") def create_menu(self, name): menu = Menu(self.mbar, name=name) self.mbar.add_cascade(label=str.capitalize(name), menu=menu) setattr(self, name + "menu", menu) getattr(self, "create_menu_" + name)(menu) def _menucmd(self, menu, label, accelerator, command): if not accelerator: menu.add_command(label=label, command=command) return underline = None if len(accelerator) == 1: # do a lot to determine the underline position underline = str.find(label, accelerator) if underline == -1: accelerator = str.lower(accelerator) underline = str.find(label, accelerator) if underline == -1: underline = None accelerator = str.upper(accelerator) menu.add_command(label=label, command=command, underline=underline, accelerator="Alt-" + accelerator) self.root.bind("<Alt-%s>" % accelerator, command) if len(accelerator) == 1: self.root.bind("<Alt-%s>" % str.lower(accelerator), command) def create_menu_file(self, menu): self._menucmd(menu, "New Window", "N", self.new_command) self._menucmd(menu, "Clone Current Window", "K", self.clone_command) self._menucmd(menu, "View Source", "V", self.view_source_command) self._menucmd(menu, 'Open Location...', "L", self.open_uri_command) self._menucmd(menu, 'Open File...', "O", self.open_file_command) self._menucmd(menu, 'Open Selection', "E", self.open_selection_command) menu.add_separator() self._menucmd(menu, "Save As...", "S", self.save_as_command) self._menucmd(menu, "Print...", "P", self.print_command) from ancillary import DocumentInfo self._menucmd(menu, "Document Info...", "D", DocumentInfo.DocumentInfoCommand(self)) menu.add_separator() self._menucmd(menu, "I/O Status Panel...", "I", self.iostatus_command) menu.add_separator() self._menucmd(menu, "Close", "W", self.close_command), if not self.app.embedded: self._menucmd(menu, "Quit", "Q", self.quit_command) def create_menu_go(self, menu): self._menucmd(menu, "Back", "Left", self.back_command) self._menucmd(menu, "Forward", "Right", self.forward_command) self._menucmd(menu, "Reload", "R", self.reload_command) menu.add_separator() self._menucmd(menu, 'History...', "H", self.show_history_command) self._menucmd(menu, "Home", None, self.home_command) def create_menu_search(self, menu): menu.grail_browser = self # Applet compatibility from ancillary import SearchMenu SearchMenu.SearchMenu(menu, self.root, self) def create_menu_bookmarks(self, menu): menu.grail_browser = self # Applet compatibility from ancillary import BookmarksGUI self.bookmarksmenu_menu = BookmarksGUI.BookmarksMenu(menu) def create_menu_preferences(self, menu): from ancillary.PrefsPanels import PrefsPanelsMenu PrefsPanelsMenu(menu, self) def create_menu_help(self, menu): lines = self.get_helpspec() i = 0 n = len(lines) - 1 while i < n: label = lines[i] i = i + 1 if label == '-': menu.add_separator() else: url = lines[i] i = i + 1 self._menucmd(menu, label, None, HelpMenuCommand(self, url)) __helpspec = None def get_helpspec(self): if self.__helpspec is not None: return self.__helpspec raw = self.app.prefs.Get('browser', 'help-menu') lines = filter(None, map(str.strip, str.split(raw, '\n'))) lines = map(str.split, lines) self.__helpspec = tuple(map(str.join, lines)) return self.__helpspec def create_urlbar(self): f = Frame(self.topframe) f.pack(fill=X) l = Label(self.root, name="uriLabel") l.pack(side=LEFT, in_=f) self.entry = Entry(self.root, name="uriEntry") self.entry.pack(side=LEFT, fill=X, expand=1, in_=f) self.entry.bind('<Return>', self.load_from_entry) def create_statusbar(self): msg_frame = Frame(self.root, name="statusbar") msg_frame.pack(fill=X, side=BOTTOM, in_=self.topframe) msg_frame.propagate(OFF) fontspec = self.app.prefs.Get('presentation', 'message-font') fontspec = str.strip(fontspec) or None self.msg = Label(self.root, font=fontspec, name="status") self.msg.pack(fill=X, in_=msg_frame) # --- External interfaces --- def get_async_image(self, src): # XXX This is here for the 0.2 ImageLoopItem applet only return self.context.get_async_image(src) def allowstop(self): self.logo_start() def clearstop(self): self.logo_stop() def clear_reset(self): num = len(self.user_menus) if num: last = self.mbar.index(END) if num > 1: self.mbar.delete(last - num + 1, last) else: self.mbar.delete(last) for b in self.user_menus: b.destroy() self.user_menus[:] = [] def set_url(self, url): self.set_entry(url) title, when = self.app.global_history.lookup_url(url) self.set_title(title or url) def set_title(self, title): self._window_title(TITLE_PREFIX + title) def message(self, string=""): self.msg['text'] = string def messagevariable(self, variable=None): if variable: self.msg['textvariable'] = variable else: self.msg['textvariable'] = "" self.msg['text'] = "" message_clear = messagevariable def error_dialog(self, exception, msg): if self.app: self.app.error_dialog(exception, msg, root=self.root) else: print("ERROR:", msg) def load(self, *args, **kw): """Interface for applets.""" return self.context.load(args, kw) def valid(self): return self.app and self in self.app.browsers # --- Internals --- def _window_title(self, title): self.root.title(title) self.root.iconname(title) def set_entry(self, url): self.entry.delete('0', END) self.entry.insert(END, url) def close(self): self.context.stop() self.viewer.close() self.root.destroy() self.bookmarksmenu_menu.close() self.bookmarksmenu_menu = None if self.app: self.app.del_browser(self) self.app.maybe_quit() # --- Callbacks --- # WM_DELETE_WINDOW on toplevel def on_delete(self): self.close() # <Return> in URL entry field def load_from_entry(self, event): url = str.strip(self.entry.get()) if url: self.context.load(grailutil.complete_url(url)) else: self.root.bell() # Stop command def stop_command(self, event=None): if self.context.busy(): self.context.stop() self.message("Stopped.") # File menu commands def new_command(self, event=None): b = Browser(self.master, self.app) return b def clone_command(self, event=None): b = Browser(self.master, self.app) b.context.clone_history_from(self.context) return b def open_uri_command(self, event=None): from ancillary import OpenURIDialog dialog = OpenURIDialog.OpenURIDialog(self.root) uri, new = dialog.go() if uri: if new: browser = Browser(self.master, self.app) else: browser = self browser.context.load(grailutil.complete_url(uri)) def open_file_command(self, event=None): from tkinter import filedialog dialog = filedialog.LoadFileDialog(self.master) filename = dialog.go(key="load") if filename: import urllib self.context.load('file:' + urllib.pathname2url(filename)) def open_selection_command(self, event=None): try: selection = self.root.selection_get() except TclError: self.root.bell() return uri = str.join(str.split(selection), '') self.context.load(grailutil.complete_url(uri)) def view_source_command(self, event=None): self.context.view_source() def save_as_command(self, event=None): self.context.save_document() def print_command(self, event=None): self.context.print_document() def iostatus_command(self, event=None): self.app.open_io_status_panel() def close_command(self, event=None): # File/Close self.close() def quit_command(self, event=None): # File/Quit if self.app: self.app.quit() else: self.close() # History menu commands def home_command(self, event=None): home = self.app.prefs.Get('landmarks', 'home-page') if not home: home = self.app.prefs.Get('landmarks', 'default-home-page') self.context.load(home) def reload_command(self, event=None): self.context.reload_page() def forward_command(self, event=None): self.context.go_forward() def back_command(self, event=None): self.context.go_back() def show_history_command(self, event=None): self.context.show_history_dialog() # --- Animated logo --- def logo_init(self): """Initialize animated logo and display the first image. This doesn't start the animation sequence -- use logo_start() for that. """ self.logo_index = 0 # Currently displayed image self.logo_last = -1 # Last image; -1 if unknown self.logo_id = None # Tk id of timer callback self.logo_animate = 1 # True if animating self.logo_next() def logo_next(self): """Display the next image in the logo animation sequence. If the first image can't be found, disable animation. """ self.logo_index = self.logo_index + 1 if self.logo_last > 0 and self.logo_index > self.logo_last: self.logo_index = 1 entytyname = "grail.logo.%d" % self.logo_index image = self.app.load_dingbat(entytyname) if not image: if self.logo_index == 1: self.logo_animate = 0 return self.logo_index = 1 entytyname = "grail.logo.%d" % self.logo_index image = self.app.load_dingbat(entytyname) if not image: self.logo_animate = 0 return self.logo.config(image=image, state=NORMAL) def logo_start(self): """Start logo animation. If we can't/don't animate the logo, enable the stop button instead. """ self.logo.config(state=NORMAL) if not self.logo_animate: return if not self.logo_id: self.logo_index = 0 self.logo_next() self.logo_id = self.root.after(200, self.logo_update) def logo_stop(self): """Stop logo animation. If we can't/don't animate the logo, disable the stop button instead. """ if not self.logo_animate: self.logo.config(state=DISABLED) return if self.logo_id: self.root.after_cancel(self.logo_id) self.logo_id = None self.logo_index = 0 self.logo_next() def logo_update(self): """Keep logo animation going.""" self.logo_id = None if self.logo_animate: self.logo_next() if self.logo_animate: self.logo_id = self.root.after(200, self.logo_update) # --- API for searching --- def search_for_pattern(self, pattern, re_flag, case_flag, backwards_flag): textwidget = self.viewer.text try: index = textwidget.index(SEL_FIRST) index = '%s + %s chars' % (str(index), backwards_flag and '0' or '1') except TclError: index = '1.0' length = IntVar(textwidget) hitlength = None hit = textwidget.search(pattern, index, count=length, nocase=not case_flag, rep=re_flag, backwards=backwards_flag) if hit: try: textwidget.tag_remove(SEL, SEL_FIRST, SEL_LAST) except TclError: pass hitlength = length.get() textwidget.tag_add(SEL, hit, "%s + %s chars" % (hit, hitlength)) textwidget.yview_pickplace(SEL_FIRST) return hit
class ContainedText(AttrElem): """Base class for a text widget contained as a cell in a canvas. Both Captions and Cells are derived from this class. """ def __init__(self, table, parentviewer, attrs): AttrElem.__init__(self, attrs) self._table = table self._container = table.container ## from profile import Profile ## from pstats import Stats ## p = Profile() ## # can't use runcall because that doesn't return the results ## p.runctx('self._viewer = Viewer(master=table.container, context=parentviewer.context, scrolling=0, stylesheet=parentviewer.stylesheet, parent=parentviewer)', ## globals(), locals()) ## Stats(p).strip_dirs().sort_stats('time').print_stats(5) self._viewer = Viewer(master=table.container, context=parentviewer.context, scrolling=0, stylesheet=parentviewer.stylesheet, parent=parentviewer) if not parentviewer.find_parentviewer(): self._viewer.RULE_WIDTH_MAGIC = self._viewer.RULE_WIDTH_MAGIC - 6 # for callback notification self._fw = self._viewer.frame self._tw = self._viewer.text self._tw.config(highlightthickness=0) self._width = 0 self._embedheight = 0 def new_formatter(self): formatter = AbstractFormatter(self._viewer) # set parskip to prevent blank line at top of cell if the content # starts with a <P> or header element. formatter.parskip = 1 return formatter def freeze(self): self._viewer.freeze() def unfreeze(self): self._viewer.unfreeze() def close(self): self._viewer.close() def maxwidth(self): return self._maxwidth # not useful until after finish() def minwidth(self): return self._minwidth # likewise def height(self): return max(self._embedheight, _get_height(self._tw)) def recalc(self): # recalculate width and height upon notification of completion # of all context's readers (usually image readers) min_nonaligned = self._minwidth maxwidth = self._maxwidth embedheight = self._embedheight # take into account all embedded windows for sub in self._viewer.subwindows: # the standard interface is used if the object has a # table_geometry() method if hasattr(sub, 'table_geometry'): submin, submax, height = sub.table_geometry() min_nonaligned = max(min_nonaligned, submin) maxwidth = max(maxwidth, submax) embedheight = max(embedheight, height) else: # this is the best we can do ## print 'non-conformant embedded window:', sub.__class__ ## print 'using generic method, which may be incorrect' geom = sub.winfo_geometry() if CELLGEOM_RE.search(geom) >= 0: [w, h, x, y] = map(grailutil.conv_integer, CELLGEOM_RE.group(1, 2, 3, 4)) min_nonaligned = max(min_nonaligned, w) # x+w? maxwidth = max(maxwidth, w) # x+w? embedheight = max(embedheight, h) # y+h? self._embedheight = embedheight self._minwidth = min_nonaligned self._maxwidth = maxwidth return len(self._viewer.subwindows) def finish(self, padding=0): # TBD: if self.layout == AUTOLAYOUT??? self._x = self._y = 0 fw = self._fw tw = self._tw # Set the padding before grabbing the width, but it could be # denoted as a percentage of the viewer width if isinstance(padding,str): try: # divide by 200 since padding is a percentage and we # want to put equal amounts of pad on both sides of # the picture. padding = int(self._table.get_available_width() * int(padding[:-1]) / 200) except ValueError: padding = 0 tw['padx'] = padding # TBD: according to the W3C table spec, minwidth should really # be max(min_left + min_right, min_nonaligned). Also note # that minwidth is recalculated by minwidth() call self._minwidth, self._maxwidth = _get_widths(self._tw) # first approximation of height. this is the best we can do # without forcing an update_idletasks() fireworks display tw['height'] = _get_linecount(tw) + 1 # initially place the cell in the canvas at position (0,0), # with the maximum width and closest approximation height. # situate() will be called later with the final layout # parameters. self._tag = self._container.create_window( 0, 0, window=fw, anchor=NW, width=self._maxwidth, height=fw['height']) def situate(self, x=0, y=0, width=None, height=None): # canvas.move() deals in relative positioning, but we want # absolute coordinates xdelta = x - self._x ydelta = y - self._y self._x = x self._y = y self._container.move(self._tag, xdelta, ydelta) if width != None and height != None: self._container.itemconfigure(self._tag, width=width, height=height) elif width != None: self._container.itemconfigure(self._tag, width=width) else: self._container.itemconfigure(self._tag, height=height)
class Browser: """The Browser class provides the top-level GUI. It is a blatant rip-off of Mosaic's look and feel, with menus, a stop button, a URL display/entry area, and (last but not least) a viewer area. But then, so are all other web browsers. :-) """ def __init__(self, master, app=None, width=None, height=None, geometry=None): self.master = master if not app: app = grailutil.get_grailapp() prefs = app.prefs self.app = app if not width: width = prefs.GetInt('browser', 'default-width') if not height: height = prefs.GetInt('browser', 'default-height') self.create_widgets(width=width, height=height, geometry=geometry) self.root.iconname('Grail') app.add_browser(self) def create_widgets(self, width, height, geometry): # I'd like to be able to set the widget name here, but I'm not # sure what the correct thing to do is. Setting it to `grail' # is definitely *not* the right thing to do since this causes # all sorts of problems. self.root = tktools.make_toplevel(self.master, class_='Grail') self._window_title("Grail: New Browser") if geometry: self.root.geometry(geometry) self.root.protocol("WM_DELETE_WINDOW", self.on_delete) self.topframe = Frame(self.root) self.topframe.pack(fill=X) self.create_logo() self.create_menubar() self.create_urlbar() self.create_statusbar() self.viewer = Viewer(self.root, browser=self, width=width, height=height) self.context = self.viewer.context if self.app.prefs.GetBoolean('browser', 'show-logo'): self.logo_init() def create_logo(self): self.logo = Button(self.root, name="logo", command=self.stop_command, state=DISABLED) self.logo.pack(side=LEFT, fill=BOTH, padx=10, pady=10, in_=self.topframe) self.root.bind("<Alt-period>", self.stop_command) self.logo_animate = 0 def create_menubar(self): # Create menu bar, menus, and menu entries # Create menu bar self.mbar = Menu(self.root, name="menubar", tearoff=0) self.root.config(menu=self.mbar) # Create the menus self.create_menu("file") self.create_menu("go") self.histmenu = self.gomenu # backward compatibility for Ping self.create_menu("search") self.create_menu("bookmarks") self.create_menu("preferences") # List of user menus (reset on page load) self.user_menus = [] if self.get_helpspec(): self.create_menu("help") def create_menu(self, name): menu = Menu(self.mbar, name=name) self.mbar.add_cascade(label=string.capitalize(name), menu=menu) setattr(self, name + "menu", menu) getattr(self, "create_menu_" + name)(menu) def _menucmd(self, menu, label, accelerator, command): if not accelerator: menu.add_command(label=label, command=command) return underline = None if len(accelerator) == 1: # do a lot to determine the underline position underline = string.find(label, accelerator) if underline == -1: accelerator = string.lower(accelerator) underline = string.find(label, accelerator) if underline == -1: underline = None accelerator = string.upper(accelerator) menu.add_command(label=label, command=command, underline=underline, accelerator="Alt-" + accelerator) self.root.bind("<Alt-%s>" % accelerator, command) if len(accelerator) == 1: self.root.bind("<Alt-%s>" % string.lower(accelerator), command) def create_menu_file(self, menu): self._menucmd(menu, "New Window", "N", self.new_command) self._menucmd(menu, "Clone Current Window", "K", self.clone_command) self._menucmd(menu, "View Source", "V", self.view_source_command) self._menucmd(menu, 'Open Location...', "L", self.open_uri_command) self._menucmd(menu, 'Open File...', "O", self.open_file_command) self._menucmd(menu, 'Open Selection', "E", self.open_selection_command) menu.add_separator() self._menucmd(menu, "Save As...", "S", self.save_as_command) self._menucmd(menu, "Print...", "P", self.print_command) import DocumentInfo self._menucmd(menu, "Document Info...", "D", DocumentInfo.DocumentInfoCommand(self)) menu.add_separator() self._menucmd(menu, "I/O Status Panel...", "I", self.iostatus_command) menu.add_separator() self._menucmd(menu, "Close", "W", self.close_command), if not self.app.embedded: self._menucmd(menu, "Quit", "Q", self.quit_command) def create_menu_go(self, menu): self._menucmd(menu, "Back", "Left", self.back_command) self._menucmd(menu, "Forward", "Right", self.forward_command) self._menucmd(menu, "Reload", "R", self.reload_command) menu.add_separator() self._menucmd(menu, 'History...', "H", self.show_history_command) self._menucmd(menu, "Home", None, self.home_command) def create_menu_search(self, menu): menu.grail_browser = self # Applet compatibility import SearchMenu SearchMenu.SearchMenu(menu, self.root, self) def create_menu_bookmarks(self, menu): menu.grail_browser = self # Applet compatibility import BookmarksGUI self.bookmarksmenu_menu = BookmarksGUI.BookmarksMenu(menu) def create_menu_preferences(self, menu): from PrefsPanels import PrefsPanelsMenu PrefsPanelsMenu(menu, self) def create_menu_help(self, menu): lines = self.get_helpspec() i = 0 n = len(lines) - 1 while i < n: label = lines[i] i = i+1 if label == '-': menu.add_separator() else: url = lines[i] i = i+1 self._menucmd(menu, label, None, HelpMenuCommand(self, url)) __helpspec = None def get_helpspec(self): if self.__helpspec is not None: return self.__helpspec raw = self.app.prefs.Get('browser', 'help-menu') lines = filter(None, map(string.strip, string.split(raw, '\n'))) lines = map(string.split, lines) self.__helpspec = tuple(map(string.join, lines)) return self.__helpspec def create_urlbar(self): f = Frame(self.topframe) f.pack(fill=X) l = Label(self.root, name="uriLabel") l.pack(side=LEFT, in_=f) self.entry = Entry(self.root, name="uriEntry") self.entry.pack(side=LEFT, fill=X, expand=1, in_=f) self.entry.bind('<Return>', self.load_from_entry) def create_statusbar(self): msg_frame = Frame(self.root, name="statusbar") msg_frame.pack(fill=X, side=BOTTOM, in_=self.topframe) msg_frame.propagate(OFF) fontspec = self.app.prefs.Get('presentation', 'message-font') fontspec = string.strip(fontspec) or None self.msg = Label(self.root, font=fontspec, name="status") self.msg.pack(fill=X, in_=msg_frame) # --- External interfaces --- def get_async_image(self, src): # XXX This is here for the 0.2 ImageLoopItem applet only return self.context.get_async_image(src) def allowstop(self): self.logo_start() def clearstop(self): self.logo_stop() def clear_reset(self): num = len(self.user_menus) if num: last = self.mbar.index(END) if num > 1: self.mbar.delete(last-num+1, last) else: self.mbar.delete(last) for b in self.user_menus: b.destroy() self.user_menus[:] = [] def set_url(self, url): self.set_entry(url) title, when = self.app.global_history.lookup_url(url) self.set_title(title or url) def set_title(self, title): self._window_title(TITLE_PREFIX + title) def message(self, string = ""): self.msg['text'] = string def messagevariable(self, variable=None): if variable: self.msg['textvariable'] = variable else: self.msg['textvariable'] = "" self.msg['text'] = "" message_clear = messagevariable def error_dialog(self, exception, msg): if self.app: self.app.error_dialog(exception, msg, root=self.root) else: print "ERROR:", msg def load(self, *args, **kw): """Interface for applets.""" return apply(self.context.load, args, kw) def valid(self): return self.app and self in self.app.browsers # --- Internals --- def _window_title(self, title): self.root.title(title) self.root.iconname(title) def set_entry(self, url): self.entry.delete('0', END) self.entry.insert(END, url) def close(self): self.context.stop() self.viewer.close() self.root.destroy() self.bookmarksmenu_menu.close() self.bookmarksmenu_menu = None if self.app: self.app.del_browser(self) self.app.maybe_quit() # --- Callbacks --- # WM_DELETE_WINDOW on toplevel def on_delete(self): self.close() # <Return> in URL entry field def load_from_entry(self, event): url = string.strip(self.entry.get()) if url: self.context.load(grailutil.complete_url(url)) else: self.root.bell() # Stop command def stop_command(self, event=None): if self.context.busy(): self.context.stop() self.message("Stopped.") # File menu commands def new_command(self, event=None): b = Browser(self.master, self.app) return b def clone_command(self, event=None): b = Browser(self.master, self.app) b.context.clone_history_from(self.context) return b def open_uri_command(self, event=None): import OpenURIDialog dialog = OpenURIDialog.OpenURIDialog(self.root) uri, new = dialog.go() if uri: if new: browser = Browser(self.master, self.app) else: browser = self browser.context.load(grailutil.complete_url(uri)) def open_file_command(self, event=None): import FileDialog dialog = FileDialog.LoadFileDialog(self.master) filename = dialog.go(key="load") if filename: import urllib self.context.load('file:' + urllib.pathname2url(filename)) def open_selection_command(self, event=None): try: selection = self.root.selection_get() except TclError: self.root.bell() return uri = string.joinfields(string.split(selection), '') self.context.load(grailutil.complete_url(uri)) def view_source_command(self, event=None): self.context.view_source() def save_as_command(self, event=None): self.context.save_document() def print_command(self, event=None): self.context.print_document() def iostatus_command(self, event=None): self.app.open_io_status_panel() def close_command(self, event=None): # File/Close self.close() def quit_command(self, event=None): # File/Quit if self.app: self.app.quit() else: self.close() # History menu commands def home_command(self, event=None): home = self.app.prefs.Get('landmarks', 'home-page') if not home: home = self.app.prefs.Get('landmarks', 'default-home-page') self.context.load(home) def reload_command(self, event=None): self.context.reload_page() def forward_command(self, event=None): self.context.go_forward() def back_command(self, event=None): self.context.go_back() def show_history_command(self, event=None): self.context.show_history_dialog() # --- Animated logo --- def logo_init(self): """Initialize animated logo and display the first image. This doesn't start the animation sequence -- use logo_start() for that. """ self.logo_index = 0 # Currently displayed image self.logo_last = -1 # Last image; -1 if unknown self.logo_id = None # Tk id of timer callback self.logo_animate = 1 # True if animating self.logo_next() def logo_next(self): """Display the next image in the logo animation sequence. If the first image can't be found, disable animation. """ self.logo_index = self.logo_index + 1 if self.logo_last > 0 and self.logo_index > self.logo_last: self.logo_index = 1 entytyname = "grail.logo.%d" % self.logo_index image = self.app.load_dingbat(entytyname) if not image: if self.logo_index == 1: self.logo_animate = 0 return self.logo_index = 1 entytyname = "grail.logo.%d" % self.logo_index image = self.app.load_dingbat(entytyname) if not image: self.logo_animate = 0 return self.logo.config(image=image, state=NORMAL) def logo_start(self): """Start logo animation. If we can't/don't animate the logo, enable the stop button instead. """ self.logo.config(state=NORMAL) if not self.logo_animate: return if not self.logo_id: self.logo_index = 0 self.logo_next() self.logo_id = self.root.after(200, self.logo_update) def logo_stop(self): """Stop logo animation. If we can't/don't animate the logo, disable the stop button instead. """ if not self.logo_animate: self.logo.config(state=DISABLED) return if self.logo_id: self.root.after_cancel(self.logo_id) self.logo_id = None self.logo_index = 0 self.logo_next() def logo_update(self): """Keep logo animation going.""" self.logo_id = None if self.logo_animate: self.logo_next() if self.logo_animate: self.logo_id = self.root.after(200, self.logo_update) # --- API for searching --- def search_for_pattern(self, pattern, regex_flag, case_flag, backwards_flag): textwidget = self.viewer.text try: index = textwidget.index(SEL_FIRST) index = '%s + %s chars' % (str(index), backwards_flag and '0' or '1') except TclError: index = '1.0' length = IntVar(textwidget) hitlength = None hit = textwidget.search(pattern, index, count=length, nocase=not case_flag, regexp=regex_flag, backwards=backwards_flag) if hit: try: textwidget.tag_remove(SEL, SEL_FIRST, SEL_LAST) except TclError: pass hitlength = length.get() textwidget.tag_add(SEL, hit, "%s + %s chars" % (hit, hitlength)) textwidget.yview_pickplace(SEL_FIRST) return hit