You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

812 lines
24 KiB
Python

import codecs
import gobject
import gtk
import gtk.gdk
import pango
from conf import conf
import parse_mirc
import windows
import servers
# Window activity Constants
HILIT = 'h'
TEXT ='t'
EVENT = 'e'
ACTIVITY_MARKUP = {
HILIT: "<span style='italic' foreground='#00F'>%s</span>",
TEXT: "<span foreground='#ca0000'>%s</span>",
EVENT: "<span foreground='#363'>%s</span>",
}
# This holds all tags for all windows ever
tag_table = gtk.TextTagTable()
link_tag = gtk.TextTag('link')
link_tag.set_property('underline', pango.UNDERLINE_SINGLE)
indent_tag = gtk.TextTag('indent')
indent_tag.set_property('indent', -20)
tag_table.add(link_tag)
tag_table.add(indent_tag)
#FIXME: MEH hates dictionaries, they remind him of the bad words
styles = {}
def style_me(widget, style):
widget.set_style(styles.get(style))
def set_style(widget_name, style):
if style:
# FIXME: find a better way...
dummy = gtk.Label()
dummy.set_style(None)
def apply_style_fg(value):
dummy.modify_text(gtk.STATE_NORMAL, gtk.gdk.color_parse(value))
def apply_style_bg(value):
dummy.modify_base(gtk.STATE_NORMAL, gtk.gdk.color_parse(value))
def apply_style_font(value):
dummy.modify_font(pango.FontDescription(value))
style_functions = (
('fg', apply_style_fg),
('bg', apply_style_bg),
('font', apply_style_font),
)
for name, f in style_functions:
if name in style:
f(style[name])
style = dummy.rc_get_style()
else:
style = None
styles[widget_name] = style
def menu_from_list(alist):
while alist and not alist[-1]:
alist.pop(-1)
last = None
for item in alist:
if item != last:
if item:
if len(item) == 2:
name, function = item
menuitem = gtk.ImageMenuItem(name)
elif len(item) == 3:
name, stock_id, function = item
if isinstance(stock_id, bool):
menuitem = gtk.CheckMenuItem(name)
menuitem.set_active(stock_id)
else:
menuitem = gtk.ImageMenuItem(stock_id)
if isinstance(function, list):
submenu = gtk.Menu()
for subitem in menu_from_list(function):
submenu.append(subitem)
menuitem.set_submenu(submenu)
else:
menuitem.connect("activate", lambda a, f: f(), function)
yield menuitem
else:
yield gtk.SeparatorMenuItem()
last = item
class Nicklist(gtk.TreeView):
def click(self, event):
if event.button == 3:
x, y = event.get_coords()
(data,), path, x, y = self.get_path_at_pos(int(x), int(y))
c_data = self.events.data(window=self.win, data=self[data], menu=[])
self.events.trigger("ListRightClick", c_data)
if c_data.menu:
menu = gtk.Menu()
for item in menu_from_list(c_data.menu):
menu.append(item)
menu.show_all()
menu.popup(None, None, None, event.button, event.time)
elif event.button == 1 and event.type == gtk.gdk._2BUTTON_PRESS:
x, y = event.get_coords()
(data,), path, x, y = self.get_path_at_pos(int(x), int(y))
self.events.trigger("ListDoubleClick", window=self.win, target=self[data])
def __getitem__(self, pos):
return self.get_model()[pos][0]
def __setitem__(self, pos, name_markup):
realname, markedupname, sortkey = name_markup
self.get_model()[pos] = realname, markedupname, sortkey
def __len__(self):
return len(self.get_model())
def index(self, item):
for i, x in enumerate(self):
if x == item:
return i
return -1
def append(self, realname, markedupname, sortkey):
self.get_model().append((realname, markedupname, sortkey))
def insert(self, pos, realname, markedupname, sortkey):
self.get_model().insert(pos, (realname, markedupname, sortkey))
def replace(self, names):
self.set_model(gtk.ListStore(str, str, str))
self.insert_column_with_attributes(
0, '', gtk.CellRendererText(), markup=1
).set_sizing(gtk.TREE_VIEW_COLUMN_FIXED)
for name in names:
self.append(*name)
self.get_model().set_sort_column_id(2, gtk.SORT_ASCENDING)
def remove(self, realname):
index = self.index(realname)
if index == -1:
raise ValueError
self.get_model().remove(self.get_model().iter_nth_child(None, index))
def clear(self):
self.get_model().clear()
def __iter__(self):
return (r[0] for r in self.get_model())
def __init__(self, window, core):
self.win = window
self.core = core
self.events = core.events
gtk.TreeView.__init__(self)
self.replace(())
self.set_headers_visible(False)
self.set_property("fixed-height-mode", True)
self.connect("button-press-event", Nicklist.click)
self.connect_after("button-release-event", lambda *a: True)
style_me(self, "nicklist")
# Label used to display/edit your current nick on a network
class NickEditor(gtk.EventBox):
def nick_change(self, entry):
oldnick, newnick = self.label.get_text(), entry.get_text()
if newnick and newnick != oldnick:
self.events.run('nick %s' % newnick, self.win, self.win.network)
self.win.input.grab_focus()
def update(self, nick=None):
self.label.set_text(nick or self.win.network.me)
def to_edit_mode(self, widget, event):
if self.label not in self.get_children():
return
if getattr(event, 'button', None) == 3:
c_data = self.events.data(window=self.win, menu=[])
self.events.trigger("NickEditMenu", c_data)
if c_data.menu:
menu = gtk.Menu()
for item in menu_from_list(c_data.menu):
menu.append(item)
menu.show_all()
menu.popup(None, None, None, event.button, event.time)
else:
entry = gtk.Entry()
entry.set_text(self.label.get_text())
entry.connect('activate', self.nick_change)
entry.connect('focus-out-event', self.to_show_mode)
self.remove(self.label)
self.add(entry)
self.window.set_cursor(None)
entry.show()
entry.grab_focus()
def to_show_mode(self, widget, event):
self.remove(widget)
self.add(self.label)
self.win.input.grab_focus()
self.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.XTERM))
def __init__(self, window, core):
gtk.EventBox.__init__(self)
self.events = core.events
self.win = window
self.label = gtk.Label()
self.label.set_padding(5, 0)
self.add(self.label)
self.connect("button-press-event", self.to_edit_mode)
self.update()
self.connect(
"realize",
lambda *a: self.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.XTERM))
)
# The entry which you type in to send messages
class TextInput(gtk.Entry):
# Generates an input event
def entered_text(self, ctrl):
#for a in globals():
# print a
#print events.__file__
#self.core.run_command(self.text)
for line in self.text.splitlines():
if line:
e_data = self.events.data(
window=self.win, network=self.win.network,
text=line, ctrl=ctrl
)
self.events.trigger('Input', e_data)
if not e_data.done:
self.events.run(line, self.win, self.win.network)
self.text = ''
def _set_selection(self, s):
if s:
self.select_region(*s)
else:
self.select_region(self.cursor, self.cursor)
#some nice toys for the scriptors
text = property(gtk.Entry.get_text, gtk.Entry.set_text)
cursor = property(gtk.Entry.get_position, gtk.Entry.set_position)
selection = property(gtk.Entry.get_selection_bounds, _set_selection)
def insert(self, text):
self.do_insert_at_cursor(self, text)
#hack to stop it selecting the text when we focus
def do_grab_focus(self):
temp = self.text, (self.selection or (self.cursor,)*2)
self.text = ''
gtk.Entry.do_grab_focus(self)
self.text, self.selection = temp
def keypress(self, event):
keychar = (
(gtk.gdk.CONTROL_MASK, '^'),
(gtk.gdk.SHIFT_MASK, '+'),
(gtk.gdk.MOD1_MASK, '!')
)
key = ''
for keymod, char in keychar:
# we make this an int, because otherwise it leaks
if int(event.state) & keymod:
key += char
key += gtk.gdk.keyval_name(event.keyval)
self.events.trigger('KeyPress', key=key, string=event.string, window=self.win)
if key == "^Return":
self.entered_text(True)
up = gtk.gdk.keyval_from_name("Up")
down = gtk.gdk.keyval_from_name("Down")
tab = gtk.gdk.keyval_from_name("Tab")
return event.keyval in (up, down, tab)
def __init__(self, window, core):
gtk.Entry.__init__(self)
self.events = core.events
self.core = core
self.win = window
# we don't want key events to propogate so we stop them in connect_after
self.connect('key-press-event', TextInput.keypress)
self.connect_after('key-press-event', lambda *a: True)
self.connect('activate', TextInput.entered_text, False)
gobject.type_register(TextInput)
def prop_to_gtk(textview, (prop, val)):
if val == parse_mirc.BOLD:
val = pango.WEIGHT_BOLD
elif val == parse_mirc.UNDERLINE:
val = pango.UNDERLINE_SINGLE
return {prop: val}
def word_from_pos(text, pos):
if text[pos] == ' ':
return ' ', pos, pos+1
else:
fr = text[:pos].split(" ")[-1]
to = text[pos:].split(" ")[0]
return fr + to, pos - len(fr), pos + len(to)
def get_iter_at_coords(view, x, y):
return view.get_iter_at_location(
*view.window_to_buffer_coords(gtk.TEXT_WINDOW_TEXT, int(x), int(y))
)
def get_event_at_iter(view, iter, core):
buffer = view.get_buffer()
line_strt = buffer.get_iter_at_line(iter.get_line())
line_end = line_strt.copy()
line_end.forward_lines(1)
pos = iter.get_line_offset()
#Caveat: text must be a unicode string, not utf-8 encoded; otherwise our
# offsets will be off when we use anything outside 7-bit ascii
#gtk.TextIter.get_text returns unicode but gtk.TextBuffer.get_text does not
text = line_strt.get_text(line_end).rstrip("\n")
word, fr, to = word_from_pos(text, pos)
return core.events.data(
window=view.win, pos=pos, text=text,
target=word, target_fr=fr, target_to=to,
)
class TextOutput(gtk.TextView):
def copy(self):
startend = self.get_buffer().get_selection_bounds()
tagsandtext = []
if startend:
start, end = startend
while not start.equal(end):
tags_at_iter = {}
for tag in start.get_tags():
try:
tagname, tagval = eval(tag.get_property('name'))
tags_at_iter[tagname] = tagval
except NameError:
continue
tagsandtext.append((dict(tags_at_iter), start.get_char()))
start.forward_char()
text = parse_mirc.unparse_mirc(tagsandtext)
gtk.clipboard_get(gtk.gdk.SELECTION_CLIPBOARD).set_text(text)
gtk.clipboard_get(gtk.gdk.SELECTION_PRIMARY).set_text(text)
return text
def clear(self):
self.get_buffer().set_text('')
def get_y(self):
rect = self.get_visible_rect()
return rect.y
def set_y(self,y):
iter = self.get_iter_at_location(0, y)
if self.get_iter_location(iter).y < y:
self.forward_display_line(iter)
yalign = float(self.get_iter_location(iter).y-y)/self.height
self.scroll_to_iter(iter, 0, True, 0, yalign)
self.check_autoscroll()
def get_ymax(self):
buffer = self.get_buffer()
return sum(self.get_line_yrange(buffer.get_end_iter())) - self.height
def get_height(self):
return self.get_visible_rect().height
y = property(get_y, set_y)
ymax = property(get_ymax)
height = property(get_height)
# the unknowing print weird things to our text widget function
def write(self, text, line_ending='\n', fg=None):
if not isinstance(text, unicode):
try:
text = codecs.utf_8_decode(text)[0]
except:
text = codecs.latin_1_decode(text)[0]
tags, text = parse_mirc.parse_mirc(text)
if fg:
tags.append({'data': ("foreground", isinstance(fg, basestring) and ('#%s'%fg) or parse_mirc.get_mirc_color(fg)), 'from': 0, 'to': len(text)})
buffer = self.get_buffer()
cc = buffer.get_char_count()
buffer.insert_with_tags_by_name(
buffer.get_end_iter(),
text + line_ending,
'indent'
)
for tag in tags:
tag_name = str(tag['data'])
if not tag_table.lookup(tag_name):
buffer.create_tag(tag_name, **prop_to_gtk(self, tag['data']))
buffer.apply_tag_by_name(
tag_name,
buffer.get_iter_at_offset(tag['from'] + cc),
buffer.get_iter_at_offset(tag['to'] + cc)
)
def popup(self, menu):
hover_iter = get_iter_at_coords(self, *self.hover_coords)
menuitems = []
if not hover_iter.ends_line():
c_data = get_event_at_iter(self, hover_iter)
c_data.menu = []
self.events.trigger("RightClick", c_data)
menuitems = c_data.menu
if not menuitems:
c_data = self.events.data(menu=[])
self.events.trigger("MainMenu", c_data)
menuitems = c_data.menu
for child in menu.get_children():
menu.remove(child)
for item in menu_from_list(menuitems):
menu.append(item)
menu.show_all()
def mousedown(self, event):
if event.button == 3:
self.hover_coords = event.get_coords()
def mouseup(self, event):
if not self.get_buffer().get_selection_bounds():
if event.button == 1:
hover_iter = get_iter_at_coords(self, event.x, event.y)
if not hover_iter.ends_line():
c_data = get_event_at_iter(self, hover_iter, self.core)
self.events.trigger("Click", c_data)
if self.is_focus():
self.win.focus()
def clear_hover(self, _event=None):
buffer = self.get_buffer()
for fr, to in self.linking:
buffer.remove_tag_by_name(
"link",
buffer.get_iter_at_mark(fr),
buffer.get_iter_at_mark(to)
)
self.linking = set()
self.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor(None)
def hover(self, event):
if self.linking:
self.clear_hover()
hover_iter = get_iter_at_coords(self, event.x, event.y)
if not hover_iter.ends_line():
h_data = get_event_at_iter(self, hover_iter, self.core)
h_data.tolink = set()
self.events.trigger("Hover", h_data)
if h_data.tolink:
buffer = self.get_buffer()
offset = buffer.get_iter_at_line(
hover_iter.get_line()
).get_offset()
for fr, to in h_data.tolink:
fr = buffer.get_iter_at_offset(offset + fr)
to = buffer.get_iter_at_offset(offset + to)
buffer.apply_tag_by_name("link", fr, to)
self.linking.add(
(buffer.create_mark(None, fr),
buffer.create_mark(None, to))
)
self.get_window(
gtk.TEXT_WINDOW_TEXT
).set_cursor(gtk.gdk.Cursor(gtk.gdk.HAND2))
self.get_pointer()
def scroll(self, _allocation=None):
if self.autoscroll:
def do_scroll():
self.scroller.value = self.scroller.upper - self.scroller.page_size
self._scrolling = False
if not self._scrolling:
self._scrolling = gobject.idle_add(do_scroll)
def check_autoscroll(self, *args):
def set_to_scroll():
self.autoscroll = self.scroller.value + self.scroller.page_size >= self.scroller.upper
gobject.idle_add(set_to_scroll)
def __init__(self, core, window, buffer=None):
if not buffer:
buffer = gtk.TextBuffer(tag_table)
gtk.TextView.__init__(self, buffer)
self.core = core
self.events = core.events
self.win = window
self.set_size_request(0, -1)
self.set_wrap_mode(gtk.WRAP_WORD_CHAR)
self.set_editable(False)
self.set_cursor_visible(False)
self.set_property("left-margin", 3)
self.set_property("right-margin", 3)
self.linking = set()
self.add_events(gtk.gdk.POINTER_MOTION_HINT_MASK)
self.add_events(gtk.gdk.LEAVE_NOTIFY_MASK)
self.connect('populate-popup', TextOutput.popup)
self.connect('motion-notify-event', TextOutput.hover)
self.connect('button-press-event', TextOutput.mousedown)
self.connect('button-release-event', TextOutput.mouseup)
self.connect_after('button-release-event', lambda *a: True)
self.connect('leave-notify-event', TextOutput.clear_hover)
self.hover_coords = 0, 0
self.autoscroll = True
self._scrolling = False
self.scroller = gtk.Adjustment()
def setup_scroll(self, _adj, vadj):
self.scroller = vadj
if vadj:
def set_scroll(adj):
self.autoscroll = adj.value + adj.page_size >= adj.upper
vadj.connect("value-changed", set_scroll)
self.connect("set-scroll-adjustments", setup_scroll)
self.connect("size-allocate", TextOutput.scroll)
def set_cursor(widget):
self.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor(None)
self.connect("realize", set_cursor)
style_me(self, "view")
class WindowLabel(gtk.EventBox):
def update(self):
title = self.win.get_title()
for escapes in (('&','&amp;'), ('<','&lt;'), ('>','&gt;')):
title = title.replace(*escapes)
for a_type in (HILIT, TEXT, EVENT):
if a_type in self.win.activity:
title = ACTIVITY_MARKUP[a_type] % title
break
self.label.set_markup(title)
def tab_popup(self, event):
if event.button == 3: # right click
c_data = self.events.data(window=self.win, menu=[])
self.events.trigger("WindowMenu", c_data)
c_data.menu += [
None,
("Close", gtk.STOCK_CLOSE, self.win.close),
]
menu = gtk.Menu()
for item in menu_from_list(c_data.menu):
menu.append(item)
menu.show_all()
menu.popup(None, None, None, event.button, event.time)
def __init__(self, window, core):
gtk.EventBox.__init__(self)
self.core = core
self.events = core.events
self.win = window
self.connect("button-press-event", WindowLabel.tab_popup)
self.label = gtk.Label()
self.add(self.label)
self.update()
self.show_all()
class FindBox(gtk.HBox):
def remove(self, *args):
self.parent.remove(self)
self.win.focus()
def clicked(self, button, search_down=False):
text = self.textbox.get_text()
if not text:
return
buffer = self.win.output.get_buffer()
if buffer.get_selection_bounds():
if button == self.down:
_, cursor_iter = buffer.get_selection_bounds()
else:
cursor_iter, _ = buffer.get_selection_bounds()
else:
cursor_iter = buffer.get_end_iter()
if search_down:
cursor = cursor_iter.forward_search(
text, gtk.TEXT_SEARCH_VISIBLE_ONLY
)
else:
cursor = cursor_iter.backward_search(
text, gtk.TEXT_SEARCH_VISIBLE_ONLY
)
if not cursor:
return
fr, to = cursor
if button == self.up:
buffer.place_cursor(fr)
self.win.output.scroll_to_iter(fr, 0)
elif button == self.down:
buffer.place_cursor(to)
self.win.output.scroll_to_iter(to, 0)
buffer.select_range(*cursor)
cursor_iter = buffer.get_iter_at_mark(buffer.get_insert())
def __init__(self, window):
gtk.HBox.__init__(self)
self.win = window
self.up = gtk.Button(stock='gtk-go-up')
self.down = gtk.Button(stock='gtk-go-down')
self.up.connect('clicked', self.clicked)
self.down.connect('clicked', self.clicked, True)
self.up.set_property('can_focus', False)
self.down.set_property('can_focus', False)
self.textbox = gtk.Entry()
self.textbox.connect('focus-out-event', self.remove)
self.textbox.connect('activate', self.clicked)
self.pack_start(gtk.Label('Find:'), expand=False)
self.pack_start(self.textbox)
self.pack_start(self.up, expand=False)
self.pack_start(self.down, expand=False)
self.show_all()
#class UrkUITabs(gtk.Window):
class UrkUITabs(object):
def __init__(self, core):
# threading stuff
gtk.gdk.threads_init()
self.core = core
self.events = core.events
self.tabs = gtk.Notebook()
self.tabs.set_property(
"tab-pos",
conf.get("ui-gtk/tab-pos", gtk.POS_BOTTOM)
)
self.tabs.set_scrollable(True)
self.tabs.set_property("can-focus", False)
self.box = gtk.VBox(False)
self.box.pack_end(self.tabs)
def __iter__(self):
return iter(self.tabs.get_children())
def __len__(self):
return self.tabs.get_n_pages()
def exit(self, *args):
self.events.trigger("Exit")
gtk.main_level() and gtk.main_quit()
def get_active(self):
return self.tabs.get_nth_page(self.tabs.get_current_page())
def set_active(self, window):
self.tabs.set_current_page(self.tabs.page_num(window))
def add(self, window):
for pos in reversed(range(self.tabs.get_n_pages())):
if self.tabs.get_nth_page(pos).network == window.network:
break
else:
pos = self.tabs.get_n_pages() - 1
self.tabs.insert_page(window, WindowLabel(window, self.core), pos+1)
def remove(self, window):
self.tabs.remove_page(self.tabs.page_num(window))
def update(self, window):
self.tabs.get_tab_label(window).update()
def show_all(self):
self.box.show_all()