#!/usr/bin/env python3 import tkinter as tk from connect import connect as conn from parser import parser import time import sys import json import os.path from io import BytesIO from PIL import Image, ImageTk import webbrowser as wb class GUI: def __init__(self): self.history = [] self.history_location = -1 self.message_bar_content = '' self.config = None self.read_config() self.conn = conn() self.parser = parser() #colors self.FG = '#E0E2E4' self.BG = '#2F393C' self.LINK = '#E8E2B7' self.ACTIVELINK = '#678CB1' self.HLB = '#804000' self.HLF = '#E0E2E4' self.STATUS_BG = '#293134' self.STATUS_FG = '#FFCD22' self.ERROR = '#E8E2B7' self.BAR_BG = '#293134' self.BAR_FG = '#2F393C' self.BAR_HLB = '#804000' self.BAR_HLF = '#E0E2E4' self.BAR_SLOT = '#E0E2E4' self.SCROLL = '#434A57' self.TYPES = '#A082BD' #create and configure root window self.root = tk.Tk(className='Burrow') self.root.title('Burrow') self.root.geometry("1200x800") self.add_assets() #main frame objects self.top_bar = tk.Frame(self.root, padx=10, height=50, relief=tk.FLAT, bd=2, bg=self.BAR_BG) self.body = tk.Frame(self.root, relief=tk.FLAT, bd=0, bg=self.BG) self.status_bar = tk.Frame(self.root, height="20", relief=tk.FLAT, bg=self.STATUS_BG, takefocus=0) #top bar objects self.btn_back = tk.Button(self.top_bar, image=self.img_back, bd=0, highlightthickness=0, takefocus=1, bg=self.BAR_BG) self.btn_forward = tk.Button(self.top_bar,image=self.img_forward, bd=0, highlightthickness=0, bg=self.BAR_BG) self.btn_favorite = tk.Button(self.top_bar,image=self.img_favorite, bd=0, highlightthickness=0, bg=self.BAR_BG) self.btn_home = tk.Button(self.top_bar, image=self.img_home, bd=0, highlightthickness=0, bg=self.BAR_BG) self.entry_url = tk.Entry(self.top_bar, selectbackground=self.HLB, selectforeground=self.HLF, highlightcolor=self.FG, highlightbackground=self.BAR_BG, fg=self.BAR_FG, bg=self.BAR_SLOT) self.btn_menu = tk.Button(self.top_bar, image=self.img_menu, bd=0, highlightthickness=0, bg=self.BAR_BG) #body objects self.scroll_bar = tk.Scrollbar(self.body, bg=self.BAR_BG, bd=0, highlightthickness=0, troughcolor=self.BG, activebackground=self.SCROLL, activerelief=tk.RAISED) self.site_display = tk.Text(self.body, bg=self.BG, foreground=self.FG, padx=20, pady=20, wrap=tk.WORD, state=tk.DISABLED, spacing2=2, spacing1=2, spacing3=2, yscrollcommand=self.scroll_bar.set, highlightcolor=self.BG, highlightbackground=self.BAR_BG, relief=tk.FLAT) self.scroll_bar.config(command=self.site_display.yview, width=20, relief=tk.RIDGE) self.site_display.tag_configure('linkcolor', foreground=self.LINK, spacing1=5, spacing2=5, spacing3=5) self.site_display.tag_configure('type_tag', background=self.BG, foreground=self.TYPES, spacing2=1, spacing1=1, spacing3=1) self.site_display.tag_configure('error_text', foreground=self.ERROR, spacing1=5, spacing2=5, spacing3=5) #status bar objects self.status_info = tk.Label(self.status_bar, textvariable=self.message_bar_content, bg=self.STATUS_BG, takefocus=0, fg=self.ACTIVELINK) self.pack_geometry() self.add_status_titles() self.add_event_listeners() #load the home screen self.load_home_screen(1) #-----------Start GUI configuration----------------------- def add_event_listeners(self): buttons = [ self.btn_back, self.btn_forward, self.btn_favorite, self.btn_home, self.btn_menu ] for x in buttons: x.bind('', self.update_status) x.bind('', self.clear_status) x.config(activebackground=self.BG) self.entry_url.bind('', self.execute_address) self.btn_back.bind('', self.go_back) self.btn_forward.bind('', self.go_forward) self.btn_home.bind('', self.load_home_screen) self.site_display.bind("", lambda event: self.site_display.yview_scroll(-1, 'units')) self.site_display.bind("", lambda event: self.site_display.yview_scroll(1, 'units')) self.site_display.bind("", lambda event: self.site_display.focus_set()) self.entry_url.bind("", lambda event: self.entry_url.focus_set()) self.root.protocol('WM_DELETE_WINDOW', self.close_window) def pack_geometry(self): self.top_bar.pack(expand=False,fill=tk.BOTH,side=tk.TOP,anchor=tk.NW) self.top_bar.pack_propagate(False) self.body.pack(expand=True,fill=tk.BOTH,side=tk.TOP) self.status_bar.pack(expand=False,fill=tk.X,side=tk.TOP,anchor=tk.SW) self.btn_back.pack(side=tk.LEFT, padx=(0,20)) self.btn_forward.pack(side=tk.LEFT, padx=(0,20)) self.btn_home.pack(side=tk.LEFT, padx=(0,20)) self.entry_url.pack(side=tk.LEFT, fill=tk.X, expand=True, ipady=5, ipadx=10) self.btn_favorite.pack(side=tk.LEFT, padx=(10,10)) self.btn_menu.pack(side=tk.LEFT) self.scroll_bar.pack(side=tk.RIGHT,fill=tk.Y) self.site_display.pack(expand=True, side=tk.TOP, fill=tk.BOTH) self.status_info.pack(side=tk.LEFT) def add_status_titles(self): self.btn_back.pop_title = 'Back' self.btn_forward.pop_title = 'Forward' self.btn_favorite.pop_title = 'Favorite' self.btn_home.pop_title = 'Home' self.btn_menu.pop_title = 'Menu' def add_assets(self): self.img_back = tk.PhotoImage(file='./images/back.png') self.img_forward = tk.PhotoImage(file='./images/forward.png') self.img_favorite = tk.PhotoImage(file='./images/favorite.png') self.img_home = tk.PhotoImage(file='./images/home.png') self.img_menu = tk.PhotoImage(file='./images/settings.png') self.message_bar_content = tk.StringVar() self.message_bar_content.set('Ready.') # ------------Start navigation methods---------------------------- def execute_address(self, event=False, btn_url=False, history=True): url = btn_url if btn_url else self.entry_url.get() if url == 'home': adjust_history = None if btn_url else 1 self.load_home_screen(adjust_history) return True parsed_url = self.parser.parse_url(url) if not parsed_url: # To do: build errors class to handle displaying errors # return errors.url_error return False if parsed_url['type'] == '7': self.send_to_screen(parsed_url, parsed_url['type']) response = self.conn.request(self.parser.resource, self.parser.host, self.parser.filetype, self.parser.port) if not response: # To do: build errors class to handle displaying errors # return errors.connection_error_NUMBER return False if history: self.history = self.history[:self.history_location+1] self.history.append(url) self.history_location = len(self.history) - 1 # Get the data to the screen self.site_display.focus_set() self.config["last_viewed"] = url self.send_to_screen(self.conn.raw_response, self.conn.filetype) return True def add_to_history(self, url): self.history = self.history[:self.history_location+1] self.history.append(url) self.history_location = len(self.history) - 1 def gotolink(self, event, href, tag_name): if href[:4] == 'http': wb.open(href, 2, True) return True element = event.widget element.tag_config(tag_name, background=self.ACTIVELINK) element.update_idletasks() # make sure change is visible time.sleep(.5) # optional delay to show changed text self.entry_url.delete(0,tk.END) self.entry_url.insert(tk.END,href) success = self.execute_address() element.tag_config(tag_name, background=self.BG) # restore tag text style element.update_idletasks() def load_home_screen(self,event=None): with open('./home.gopher','r') as f: data = f.read() self.entry_url.delete(0, tk.END) self.entry_url.insert(tk.END, 'home') if event is not None: self.add_to_history('home') self.send_to_screen(data, '1') def go_back(self, event): if len(self.history) <= 1 or self.history_location <= 0: return False self.history_location -= 1 href = self.history[self.history_location] self.populate_url_bar(href) self.execute_address(False, href, False) def go_forward(self, event): if len(self.history) <= 1 or self.history_location >= len(self.history) - 1: return False self.history_location += 1 href = self.history[self.history_location] self.populate_url_bar(href) self.execute_address(False, href, False) #-------------Start view methods---------------- def load_favorites(self): header = 'i#############\tfalse\tnull.host\t1\r\ni manually edit in go.config.json\tfalse\tnull.host\t1\r\n or add using the favorites button\tfalse\tnull.host\t1\r\ni\tfalse\tnull.host\t1\r\n' #soon add code to load in favorites here self.send_to_screen(data=header, clear=False) def show_menu(self, data, clear=True): if not data: #error handling will go here return False types = { '0': '( TXT )', '1': '( MNU )', '3': '( ERR )', '7': '( INT )', '9': '( BIN )', 'g': '( GIF )', 'I': '( IMG )', 'h': '( HTM )', 'i': '( INF )', 's': '( SND )', 'p': '( PNG )' } self.site_display.config(state=tk.NORMAL) if clear: self.site_display.delete(1.0, tk.END) link_count = 0 for x in data[1:]: if x['type'] == 'i': self.site_display.insert(tk.END,' \t\t{}\n'.format(x['description'])) elif x['type'] == '3': self.site_display.insert(tk.END,' \t\t{}\n'.format(x['description'])) elif x['type'] in types: # adapted from: # https://stackoverflow.com/questions/27760561/tkinter-and-hyperlinks if x['port'] and x['port'][0] != ':': x['port'] = ':{}'.format(x['port']) if x['type'] == 'h': link = '{}/{}'.format(x['host'], x['resource']) link = 'http://{}'.format(link.replace('//','/')) else: link = 'gopher://{}{}/{}{}'.format(x['host'], x['port'], x['type'], x['resource']) tag_name = 'link{}'.format(link_count) callback = (lambda event, href=link, tag_name=tag_name: self.gotolink(event, href, tag_name)) hover = (lambda event, href=link, tag_name=tag_name: self.hoverlink(event, href, tag_name)) clear = (lambda event, tag_name=tag_name: self.clear_status(event, tag_name)) self.site_display.tag_bind(tag_name, "", callback) self.site_display.tag_bind(tag_name, "", hover) self.site_display.tag_bind(tag_name, '', clear) self.site_display.insert(tk.END, types[x['type']], ('type_tag',)) self.site_display.insert(tk.END,'\t\t') self.site_display.insert(tk.END, x['description'], (tag_name,'linkcolor')) self.site_display.insert(tk.END, '\n') link_count += 1 self.site_display.config(state=tk.DISABLED) return True def show_text(self, data): self.site_display.config(state=tk.NORMAL) self.site_display.delete(1.0, tk.END) self.site_display.insert(tk.END, data[:-2]) self.site_display.config(state=tk.DISABLED) def show_image(self, data): self.current_image = self.build_image(data) self.site_display.config(state=tk.NORMAL) self.site_display.delete(1.0, tk.END) self.site_display.image_create(tk.END, image = self.current_image) self.site_display.config(state=tk.DISABLED) def send_to_screen(self, data, itemtype='1', clear=True): if itemtype == '0': self.show_text(data) elif itemtype in ['1','3']: data = self.parser.parse_menu(data) self.show_menu(data, clear) elif itemtype in ['p','I','g']: self.show_image(data) elif itemtype == '7': pass def update_status(self, event, href=False): if href: self.message_bar_content.set(href) else: self.message_bar_content.set(event.widget.pop_title) def clear_status(self, event, tag_name=False): if tag_name: e = event.widget e.tag_config(tag_name, underline=0) self.site_display.config(cursor='xterm') e.update_idletasks() self.message_bar_content.set('') def populate_url_bar(self, url): self.entry_url.delete(0, tk.END) self.entry_url.insert(tk.END, url) def hoverlink(self, event, href, tag_name): self.update_status(event, href) e = event.widget e.tag_config(tag_name, underline=1, foreground=self.LINK) self.site_display.config(cursor="arrow") e.update_idletasks() def build_image(self, bytes_str): stream = BytesIO(bytes_str) pilimage = Image.open(stream) tkimage = ImageTk.PhotoImage(pilimage) return tkimage #--------Start file handling methods------------ def read_config(self, url='./go.config.json'): if not os.path.isfile(url): self.create_config() with open('./go.config.json', 'r') as f: config = f.read() config = json.loads(config) self.config = config def write_config(self, config, url='./go.config.json'): with open(url, 'w') as f: data = json.dumps(config) f.write(data) def create_config(self): config = {"favorites": [],"last_viewed": None} self.write_config(config) def close_window(self): self.write_config(self.config) self.root.destroy() if __name__ == '__main__': app = GUI() app.root.mainloop()