Merge pull request #4 from sloumdrone/refactor-classes
Refactor classes
BIN
btn_back.png
Before Width: | Height: | Size: 2.8 KiB |
BIN
btn_forward.png
Before Width: | Height: | Size: 2.7 KiB |
BIN
btn_home.png
Before Width: | Height: | Size: 1.7 KiB |
BIN
btn_menu.png
Before Width: | Height: | Size: 4.4 KiB |
BIN
btn_refresh.png
Before Width: | Height: | Size: 1.8 KiB |
|
@ -0,0 +1,13 @@
|
|||
#!/usr/bin/env python3
|
||||
################################################
|
||||
## Burrow, a gopher client/browser
|
||||
## - - - - - - - - - - - - - - - - - - - - - - -
|
||||
## Version 0.2.1
|
||||
################################################
|
||||
|
||||
from gui import GUI
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
app = GUI()
|
||||
app.root.mainloop()
|
97
conn.py
|
@ -1,97 +0,0 @@
|
|||
import socket
|
||||
import sys
|
||||
import re
|
||||
|
||||
class Tunnel:
|
||||
def __init__(self):
|
||||
self.raw_request = None
|
||||
self.text_output = None
|
||||
self.types = {
|
||||
'0': '(TXT)',
|
||||
'1': '(MENU)',
|
||||
'2': None,
|
||||
'3': 'Error code',
|
||||
'4': None,
|
||||
'5': None,
|
||||
'6': None,
|
||||
'7': '(INTER)',
|
||||
'8': 'Telnet',
|
||||
'9': '(BIN)',
|
||||
'+': None,
|
||||
'g': '(GIF)',
|
||||
'I': '(IMG)',
|
||||
't': None,
|
||||
'h': '(HTML)',
|
||||
'i': '(INFO)',
|
||||
's': '(SOUND)'
|
||||
}
|
||||
|
||||
|
||||
def make_connection(self, resource, host, itemtype, port=70):
|
||||
endline = '\r\n'
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
s.settimeout(20.0)
|
||||
try:
|
||||
port = int(port)
|
||||
except:
|
||||
port = 70
|
||||
s.connect((host, port))
|
||||
s.sendall((resource + '\r\n').encode('utf-8'))
|
||||
r = s.makefile(mode = 'r')
|
||||
raw_data = r.read()
|
||||
try:
|
||||
data = raw_data.decode('utf-8')
|
||||
except AttributeError:
|
||||
data = raw_data
|
||||
self.raw_request = data
|
||||
if itemtype[1] == '1':
|
||||
#handle menus
|
||||
self.text_output = self.gopher_to_text()
|
||||
elif itemtype[1] == '0':
|
||||
#handle text files
|
||||
self.text_output = [self.raw_request]
|
||||
self.text_output.insert(0,itemtype[1])
|
||||
s.close()
|
||||
|
||||
|
||||
|
||||
def gopher_to_text(self):
|
||||
message = self.raw_request.split('\n')
|
||||
message = [x.split('\t') for x in message]
|
||||
message = [{'type': x[0][0], 'description': x[0][1:], 'resource': x[1], 'host': x[2], 'port': x[3]} for x in message if len(x) >= 4]
|
||||
return message
|
||||
|
||||
|
||||
def parse_url(self, url):
|
||||
regex = r'^(?P<protocol>(?:gopher:\/\/)?)?(?P<host>[\w\.\d]+)(?P<port>(?::\d+)?)?(?P<type>(?:\/\d)?)?(?P<resource>(?:\/[\w\/\d\-?.]*)?)?$'
|
||||
match = re.match(regex, url)
|
||||
|
||||
protocol = match.group('protocol')
|
||||
itemtype = match.group('type')
|
||||
host = match.group('host')
|
||||
port = match.group('port')
|
||||
resource = match.group('resource')
|
||||
|
||||
if protocol != 'gopher://' and protocol:
|
||||
return False
|
||||
if itemtype and not self.types[itemtype[1]]:
|
||||
return False
|
||||
elif not itemtype:
|
||||
itemtype = '/1'
|
||||
if not host:
|
||||
return False
|
||||
if not resource:
|
||||
resource = '/'
|
||||
if port:
|
||||
port = port[1:]
|
||||
|
||||
self.make_connection(resource, host, itemtype, port)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
inp = sys.argv[1:]
|
||||
if len(inp) >= 2:
|
||||
test = Tunnel()
|
||||
test.make_connection(inp[1],inp[0],'70')
|
||||
else:
|
||||
print('Incorrect request')
|
|
@ -0,0 +1,29 @@
|
|||
import socket
|
||||
|
||||
class connect:
|
||||
def __init__(self):
|
||||
self.raw_response = None
|
||||
self.filetype = None
|
||||
|
||||
def request(self, resource, host, itemtype, port=70):
|
||||
#connects to server and returns list with response type and body
|
||||
socket_conn = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
socket_conn.settimeout(20.0)
|
||||
socket_conn.connect((host, port))
|
||||
socket_conn.sendall((resource + '\r\n').encode('utf-8'))
|
||||
|
||||
if itemtype in ['I','p','g']:
|
||||
response = socket_conn.makefile(mode = 'rb', errors = 'ignore')
|
||||
else:
|
||||
response = socket_conn.makefile(mode = 'r', errors = 'ignore')
|
||||
|
||||
try:
|
||||
self.raw_response = response.read()
|
||||
self.filetype = itemtype
|
||||
except UnicodeDecodeError:
|
||||
self.raw_response = '3Error decoding server response\tfalse\tnull.host\t1'
|
||||
self.filetype = '3'
|
||||
|
||||
socket_conn.close()
|
||||
|
||||
return {'type': self.filetype, 'body': self.raw_response}
|
236
digger.py
|
@ -1,236 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
import tkinter as tk
|
||||
from conn import Tunnel as go
|
||||
import time
|
||||
import sys
|
||||
|
||||
class GUI:
|
||||
def __init__(self):
|
||||
self.history = []
|
||||
self.history_location = -1
|
||||
self.message_bar_content = ''
|
||||
|
||||
#colors
|
||||
self.FG = 'black'
|
||||
self.BG = 'white'
|
||||
self.LINK = 'blue'
|
||||
self.ACTIVELINK = 'red'
|
||||
self.HLB = '#e37800'
|
||||
self.HLF = 'white'
|
||||
self.STATUS = 'silver'
|
||||
|
||||
#create and configure root window
|
||||
self.root = tk.Tk(className='Digger')
|
||||
self.root.title('Digger')
|
||||
self.root.geometry("1200x800")
|
||||
self.add_assets()
|
||||
|
||||
#main frame objects
|
||||
self.top_bar = tk.Frame(self.root, padx=10, height=70, relief=tk.FLAT, bd=2)
|
||||
self.body = tk.Frame(self.root, relief=tk.RIDGE, bd=2)
|
||||
self.status_bar = tk.Frame(self.root, height="20", bg=self.STATUS, takefocus=0)
|
||||
|
||||
#top bar objects
|
||||
self.btn_back = tk.Button(self.top_bar, image=self.img_back, bd=0, highlightthickness=0, takefocus=1)
|
||||
self.btn_forward = tk.Button(self.top_bar,image=self.img_forward, bd=0, highlightthickness=0)
|
||||
self.btn_refresh = tk.Button(self.top_bar,image=self.img_refresh, bd=0, highlightthickness=0)
|
||||
self.btn_home = tk.Button(self.top_bar, image=self.img_home, bd=0, highlightthickness=0)
|
||||
self.entry_url = tk.Entry(self.top_bar, selectbackground=self.HLB, selectforeground=self.HLF, highlightcolor=self.HLB)
|
||||
self.btn_menu = tk.Button(self.top_bar, image=self.img_menu, bd=0, highlightthickness=0)
|
||||
|
||||
#body objects
|
||||
self.site_display = tk.Text(self.body, bg=self.BG, foreground=self.FG, padx=20, pady=20, wrap=tk.WORD, state=tk.DISABLED, spacing2=5, spacing1=5)
|
||||
self.site_display.tag_configure('linkcolor', foreground=self.LINK, spacing1=5)
|
||||
self.site_display.tag_configure('type_tag', background=self.FG, foreground=self.BG, spacing2=0, spacing1=0)
|
||||
|
||||
#status bar objects
|
||||
self.status_info = tk.Label(self.status_bar, textvariable=self.message_bar_content, bg=self.STATUS, takefocus=0)
|
||||
|
||||
self.pack_geometry()
|
||||
self.add_status_titles()
|
||||
self.add_event_listeners()
|
||||
|
||||
#---------------------------------------------------
|
||||
|
||||
def add_event_listeners(self):
|
||||
buttons = [
|
||||
self.btn_back,
|
||||
self.btn_forward,
|
||||
self.btn_refresh,
|
||||
self.btn_home,
|
||||
self.btn_menu
|
||||
]
|
||||
|
||||
for x in buttons:
|
||||
x.bind('<Enter>', self.update_status)
|
||||
x.bind('<Leave>', self.clear_status)
|
||||
self.entry_url.bind('<Return>', self.execute_address)
|
||||
self.btn_back.bind('<Button-1>', self.go_back)
|
||||
self.btn_forward.bind('<Button-1>', self.go_forward)
|
||||
|
||||
|
||||
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_refresh.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=10, ipadx=10)
|
||||
self.btn_menu.pack(side=tk.LEFT, padx=(10,0))
|
||||
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_refresh.pop_title = 'Refresh'
|
||||
self.btn_home.pop_title = 'Home'
|
||||
self.btn_menu.pop_title = 'Menu'
|
||||
|
||||
|
||||
def add_assets(self):
|
||||
self.img_back = tk.PhotoImage(file='./btn_back.png')
|
||||
self.img_forward = tk.PhotoImage(file='./btn_forward.png')
|
||||
self.img_refresh = tk.PhotoImage(file='./btn_refresh.png')
|
||||
self.img_menu = tk.PhotoImage(file='./btn_menu.png')
|
||||
self.img_home = tk.PhotoImage(file='./btn_home.png')
|
||||
self.img_menu = tk.PhotoImage(file='./btn_menu.png')
|
||||
self.message_bar_content = tk.StringVar()
|
||||
self.message_bar_content.set('Ready.')
|
||||
|
||||
|
||||
def execute_address(self, event, btn_url=False):
|
||||
if btn_url and btn_url != -1:
|
||||
url = btn_url
|
||||
elif btn_url and btn_url == -1:
|
||||
return
|
||||
else:
|
||||
url = self.entry_url.get()
|
||||
self.history = self.history[:self.history_location+1]
|
||||
self.history.append(url)
|
||||
self.history_location = len(self.history) - 1
|
||||
self.site_display.focus_set()
|
||||
request = go()
|
||||
request.parse_url(url)
|
||||
self.send_to_screen(request.text_output)
|
||||
|
||||
|
||||
def gotolink(self, event, href, tag_name):
|
||||
res = event.widget
|
||||
res.tag_config(tag_name, background=self.ACTIVELINK) # change tag text style
|
||||
res.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)
|
||||
self.execute_address(0) #the zero is meaningless, but the function expects a param
|
||||
res.tag_config(tag_name, background=self.BG) # restore tag text style
|
||||
res.update_idletasks()
|
||||
|
||||
|
||||
def hoverlink(self, event, href, tag_name):
|
||||
self.update_status(event, href)
|
||||
e = event.widget
|
||||
e.tag_config(tag_name, underline=1)
|
||||
e.update_idletasks()
|
||||
|
||||
|
||||
def send_to_screen(self, data):
|
||||
link_count = 0
|
||||
self.site_display.config(state=tk.NORMAL)
|
||||
self.site_display.delete(1.0, tk.END)
|
||||
types = {
|
||||
'0': '( TEXT )',
|
||||
'1': '( MENU )',
|
||||
'2': None,
|
||||
'3': 'Error code',
|
||||
'4': None,
|
||||
'5': None,
|
||||
'6': None,
|
||||
'7': '( INTR )',
|
||||
'8': 'Telnet',
|
||||
'9': '( BIN )',
|
||||
'+': None,
|
||||
'g': '( GIF )',
|
||||
'I': '( IMG )',
|
||||
't': None,
|
||||
'h': '( HTML )',
|
||||
'i': '( INFO )',
|
||||
's': '( SOUND)'
|
||||
}
|
||||
|
||||
if data[0] == '0':
|
||||
self.site_display.insert(tk.END, data[1])
|
||||
elif data[0] == '1':
|
||||
for x in data[1:]:
|
||||
if x['type'] == 'i':
|
||||
self.site_display.insert(tk.END,' \t\t{}\n'.format(x['description']))
|
||||
else:
|
||||
# adapted from:
|
||||
# https://stackoverflow.com/questions/27760561/tkinter-and-hyperlinks
|
||||
if x['port'] and x['port'][0] != ':':
|
||||
x['port'] = ':{}'.format(x['port'])
|
||||
|
||||
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, "<Button-1>", callback)
|
||||
self.site_display.tag_bind(tag_name, "<Enter>", hover)
|
||||
self.site_display.tag_bind(tag_name, '<Leave>', 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)
|
||||
|
||||
|
||||
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)
|
||||
e.update_idletasks()
|
||||
self.message_bar_content.set('')
|
||||
|
||||
|
||||
def go_back(self, event):
|
||||
if len(self.history) > 1 and self.history_location > 0:
|
||||
self.history_location -= 1
|
||||
href = self.history[self.history_location]
|
||||
self.entry_url.delete(0, tk.END)
|
||||
self.entry_url.insert(tk.END, href)
|
||||
else:
|
||||
href = -1
|
||||
self.execute_address(False, href)
|
||||
|
||||
|
||||
def go_forward(self, event):
|
||||
if len(self.history) > 1 and self.history_location < len(self.history) - 1:
|
||||
self.history_location += 1
|
||||
href = self.history[self.history_location]
|
||||
self.entry_url.delete(0,tk.END)
|
||||
self.entry_url.insert(tk.END, href)
|
||||
else:
|
||||
href = -1
|
||||
self.execute_address(False, href)
|
||||
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
app = GUI()
|
||||
app.root.mainloop()
|
||||
|
||||
|
|
@ -0,0 +1 @@
|
|||
{"favorites": [], "last_viewed": "gopher://gopher.floodgap.com:70/1/"}
|
|
@ -0,0 +1,389 @@
|
|||
#!/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
|
||||
|
||||
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='Digger')
|
||||
self.root.title('Digger')
|
||||
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('<Enter>', self.update_status)
|
||||
x.bind('<Leave>', self.clear_status)
|
||||
x.config(activebackground=self.BG)
|
||||
self.entry_url.bind('<Return>', self.execute_address)
|
||||
self.btn_back.bind('<Button-1>', self.go_back)
|
||||
self.btn_forward.bind('<Button-1>', self.go_forward)
|
||||
self.btn_home.bind('<Button-1>', self.load_home_screen)
|
||||
self.site_display.bind("<Up>", lambda event: self.site_display.yview_scroll(-1, 'units'))
|
||||
self.site_display.bind("<Down>", lambda event: self.site_display.yview_scroll(1, 'units'))
|
||||
self.site_display.bind("<Button-1>", lambda event: self.site_display.focus_set())
|
||||
self.entry_url.bind("<Button-1>", 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):
|
||||
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'])
|
||||
|
||||
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, "<Button-1>", callback)
|
||||
self.site_display.tag_bind(tag_name, "<Enter>", hover)
|
||||
self.site_display.tag_bind(tag_name, '<Leave>', 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()
|
||||
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
i false null.host 1
|
||||
i██████╗ ██╗ ██╗██████╗ ██████╗ ██████╗ ██╗ ██╗ false null.host 1
|
||||
i██╔══██╗██║ ██║██╔══██╗██╔══██╗██╔═══██╗██║ ██║ false null.host 1
|
||||
i██████╔╝██║ ██║██████╔╝██████╔╝██║ ██║██║ █╗ ██║ false null.host 1
|
||||
i██╔══██╗██║ ██║██╔══██╗██╔══██╗██║ ██║██║███╗██║ false null.host 1
|
||||
i██████╔╝╚██████╔╝██║ ██║██║ ██║╚██████╔╝╚███╔███╔╝ false null.host 1
|
||||
i╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝ ╚═════╝ ╚══╝╚══╝ false null.host 1
|
||||
i false null.host 1
|
||||
i##########CONTENT PORTALS########## false null.host 1
|
||||
i false null.host 1
|
||||
1Floodgap / gopher.floodgap.com 70
|
||||
1Super Dimensional Fortress / sdf.org 70
|
||||
i false null.host 1
|
||||
i false null.host 1
|
||||
i#############FAVORITES############# false null.host 1
|
||||
i manually edit in favorites.json false null.host 1
|
||||
i or add using the favorites button false null.host 1
|
||||
i false null.host 1
|
After Width: | Height: | Size: 979 B |
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 3.3 KiB |
After Width: | Height: | Size: 1.0 KiB |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 963 B |
|
@ -0,0 +1,54 @@
|
|||
import re
|
||||
|
||||
# Handles parsing gopher data:
|
||||
# URLs, Menus
|
||||
|
||||
class parser:
|
||||
|
||||
def __init__(self):
|
||||
self.host = None
|
||||
self.resource = None
|
||||
self.filetype = None
|
||||
self.protocol = None
|
||||
self.port = None
|
||||
self.menu = None
|
||||
|
||||
def parse_url(self, url):
|
||||
# Take in a URL and output a dict of the url parts
|
||||
|
||||
regex = r'^(?P<protocol>(?:gopher:\/\/)?)?(?P<host>[\w\.\d]+)(?P<port>(?::\d+)?)?(?P<type>(?:\/[\dgIp])?)?(?P<resource>(?:\/[\w\/\d\-?.]*)?)?$'
|
||||
|
||||
match = re.match(regex, url)
|
||||
protocol = match.group('protocol')
|
||||
itemtype = match.group('type')
|
||||
host = match.group('host')
|
||||
port = match.group('port')
|
||||
resource = match.group('resource')
|
||||
|
||||
if not host:
|
||||
return False
|
||||
|
||||
if not resource:
|
||||
resource = '/'
|
||||
|
||||
|
||||
self.filetype = itemtype[len(itemtype) - 1] if itemtype else '1'
|
||||
self.protocol = protocol if protocol else 'gopher://'
|
||||
self.port = int(port[1:]) if port else 70
|
||||
self.host = host
|
||||
self.resource = resource
|
||||
|
||||
return {'host': self.host, 'resource': self.resource, 'type': self.filetype, 'protocol': self.protocol, 'port': self.port}
|
||||
|
||||
|
||||
def parse_menu(self, text):
|
||||
# Take in text from connection and output a list
|
||||
# w/ objects representing each menu item
|
||||
|
||||
message_list = text.split('\n')
|
||||
message_list = [x.split('\t') for x in message_list]
|
||||
message_list = [{'type': x[0][0], 'description': x[0][1:], 'resource': x[1], 'host': x[2], 'port': x[3]} for x in message_list if len(x) >= 4]
|
||||
self.menu = message_list
|
||||
|
||||
return message_list
|
||||
|