From a74a928764e60d284f27a81db3dc03203636767e Mon Sep 17 00:00:00 2001 From: Jonathan Harris Date: Tue, 13 Oct 2015 16:50:05 +0100 Subject: [PATCH] Split out ttk HyperlinkLabel widget into a separate file and generalise. --- EDMarketConnector.py | 51 ++---------------------- ttkHyperlinkLabel.py | 95 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 98 insertions(+), 48 deletions(-) create mode 100644 ttkHyperlinkLabel.py diff --git a/EDMarketConnector.py b/EDMarketConnector.py index b5f83c3c..45283453 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -8,12 +8,11 @@ from os.path import expanduser, isdir, join import re import requests from time import time, localtime, strftime -import urllib -import webbrowser import Tkinter as tk import ttk import tkFont +from ttkHyperlinkLabel import HyperlinkLabel if __debug__: from traceback import print_exc @@ -39,50 +38,6 @@ SERVER_RETRY = 5 # retry pause for Companion servers [s] EDSM_POLL = 0.1 -class HyperlinkLabel(ttk.Label): - - def __init__(self, master=None, **kw): - self.urlfn = kw.pop('urlfn', None) - ttk.Label.__init__(self, master, **kw) - self.font_n = kw.get('font', ttk.Style().lookup('TLabel', 'font')) - self.font_u = tkFont.Font(self, self.font_n) - self.font_u.configure(underline = True) - self.menu = tk.Menu(None, tearoff=tk.FALSE) - self.menu.add_command(label=_('Copy'), command = self.copy) # As in Copy and Paste - self.bind('', self._enter) - self.bind('', self._leave) - self.bind('', self._click) - self.bind(platform == 'darwin' and '' or '', self._contextmenu) - - # Make blue and clickable if setting non-empty text - def __setitem__(self, key, value): - if key=='text': - if self.urlfn and value: - self.configure({key: value}, foreground = 'blue', cursor = platform=='darwin' and 'pointinghand' or 'hand2') - else: - self.configure({key: value}, foreground = '', cursor = 'arrow') - else: - self.configure({key: value}) - - def _enter(self, event): - self.configure(font = self.font_u) - - def _leave(self, event): - self.configure(font = self.font_n) - - def _click(self, event): - if self.urlfn and self['text']: - webbrowser.open(self.urlfn(self['text'])) - - def _contextmenu(self, event): - if self['text'] and self['text'] != AppWindow.STATION_UNDOCKED: - self.menu.post(platform == 'darwin' and event.x_root + 1 or event.x_root, event.y_root) - - def copy(self): - self.clipboard_clear() - self.clipboard_append(self['text']) - - class AppWindow: STATION_UNDOCKED = u'×' # "Station" name to display when not docked = U+00D7 @@ -127,8 +82,8 @@ class AppWindow: ttk.Label(frame, text=_('Station:')).grid(row=2, column=0, sticky=tk.W) # Main window self.cmdr = ttk.Label(frame, width=-21) - self.system = HyperlinkLabel(frame, compound=tk.RIGHT, urlfn = self.system_url) - self.station = HyperlinkLabel(frame, urlfn = self.station_url) + self.system = HyperlinkLabel(frame, compound=tk.RIGHT, url = self.system_url, popup_copy = True) + self.station = HyperlinkLabel(frame, url = self.station_url, popup_copy = lambda x: x!=self.STATION_UNDOCKED) self.button = ttk.Button(frame, text=_('Update'), command=self.getandsend, default=tk.ACTIVE, state=tk.DISABLED) # Update button in main window self.status = ttk.Label(frame, width=-25) self.w.bind('', self.getandsend) diff --git a/ttkHyperlinkLabel.py b/ttkHyperlinkLabel.py new file mode 100644 index 00000000..4af9f9b1 --- /dev/null +++ b/ttkHyperlinkLabel.py @@ -0,0 +1,95 @@ +from sys import platform +import webbrowser + +import Tkinter as tk +import ttk +import tkFont + + +# A clickable ttk Label +# +# In addition to standard ttk.Label arguments, takes the following arguments: +# url: The URL as a string that the user will be sent to on clicking on non-empty label text. If url is a function it will be called on click with the current label text and should return the URL as a string. +# underline: If True/False the text is always/never underlined. If None (the default) the text is underlined only on hover. +# popup_copy: Whether right-click on non-empty label text pops up a context menu with a 'Copy' option. Defaults to no context menu. If popup_copy is a function it will be called with the current label text and should a boolean. +# +class HyperlinkLabel(ttk.Label, object): + + def __init__(self, master=None, **kw): + self.url = kw.pop('url') + self.popup_copy = kw.pop('popup_copy', False) + self.underline = kw.pop('underline', None) # override ttk.Label's underline + self.foreground = kw.get('foreground') or 'blue' + self.disabledforeground = kw.pop('disabledforeground', None) or 'SystemWindowText' + + ttk.Label.__init__(self, master, **kw) + + if self.url: + self.bind('', self._click) + + if self.popup_copy: + self.menu = tk.Menu(None, tearoff=tk.FALSE) + self.menu.add_command(label=_('Copy'), command = self.copy) # As in Copy and Paste + self.bind(platform == 'darwin' and '' or '', self._contextmenu) + + if self.underline is not False: + self.font_n = kw.get('font', ttk.Style().lookup('TLabel', 'font')) + self.font_u = tkFont.Font(self, self.font_n) + self.font_u.configure(underline = True) + if self.underline is True: + self.configure(font = self.font_u) + else: + self.bind('', self._enter) + self.bind('', self._leave) + + self.configure(state = kw.get('state'), text = kw.get('text')) # set up initial appearance + + # Change cursor and appearance depending on state and text + def configure(self, cnf=None, **kw): + if kw.get('state') == tk.DISABLED: + if 'foreground' not in kw: + kw['foreground'] = self.disabledforeground + if self.underline is not False and 'font' not in kw: + kw['font'] = self.font_n + if 'cursor' not in kw: + kw['cursor'] = 'arrow' # System default + elif 'state' in kw: + if 'foreground' not in kw: + kw['foreground'] = self.foreground + if self.underline is True and 'font' not in kw: + kw['font'] = self.font_u + + # Hover cursor only if widget is enabled and text is non-empty + if ('text' in kw or 'state' in kw) and 'cursor' not in kw: + if self.url and (kw['text'] if 'text' in kw else self['text']) and (kw['state'] if 'state' in kw else str(self['state']))!=tk.DISABLED: + kw['cursor'] = platform=='darwin' and 'pointinghand' or 'hand2' + else: + kw['cursor'] = 'arrow' # System default + + super(HyperlinkLabel, self).configure(cnf, **kw) + + def __setitem__(self, key, value): + self.configure(None, **{key: value}) + + def _enter(self, event): + if str(self['state']) != tk.DISABLED: + self.configure(font = self.font_u) + + def _leave(self, event): + self.configure(font = self.font_n) + + def _click(self, event): + if self['text'] and str(self['state']) != tk.DISABLED: + url = self.url(self['text']) if callable(self.url) else self.url + if url: + webbrowser.open(url) + + def _contextmenu(self, event): + if self['text'] and (self.popup_copy(self['text']) if callable(self.popup_copy) else self.popup_copy): + self.menu.post(platform == 'darwin' and event.x_root + 1 or event.x_root, event.y_root) + + def copy(self): + self.clipboard_clear() + self.clipboard_append(self['text']) + +