383 lines
8.9 KiB
Ruby
383 lines
8.9 KiB
Ruby
|
#! /usr/bin/env ruby
|
||
|
=begin
|
||
|
main.rb - Main class of gtk-demo.
|
||
|
|
||
|
Copyright (c) 2003-2006 Ruby-GNOME2 Project Team
|
||
|
This program is licenced under the same licence as Ruby-GNOME2.
|
||
|
|
||
|
$Id: main.rb,v 1.20 2007/07/09 13:21:54 ggc Exp $
|
||
|
=end
|
||
|
|
||
|
require 'gtk2'
|
||
|
|
||
|
$:.unshift(File.dirname(__FILE__))
|
||
|
|
||
|
module Demo
|
||
|
|
||
|
class Main < Gtk::Window
|
||
|
TITLE_COLUMN, FILENAME_COLUMN, CLASS_COLUMN, ITALIC_COLUMN = 0, 1, 2, 3
|
||
|
|
||
|
def initialize
|
||
|
super()
|
||
|
|
||
|
@current_file = nil
|
||
|
|
||
|
@info_buffer = Gtk::TextBuffer.new
|
||
|
@source_buffer = Gtk::TextBuffer.new
|
||
|
|
||
|
set_title('Ruby/GTK+ Code Demos')
|
||
|
signal_connect('destroy') do
|
||
|
Gtk.main_quit
|
||
|
end
|
||
|
|
||
|
signal_connect("key_press_event") do |widget, event|
|
||
|
if event.state.control_mask? and event.keyval == Gdk::Keyval::GDK_q
|
||
|
destroy
|
||
|
true
|
||
|
else
|
||
|
false
|
||
|
end
|
||
|
end
|
||
|
|
||
|
hbox = Gtk::HBox.new
|
||
|
add(hbox)
|
||
|
|
||
|
tree = create_tree
|
||
|
scrolled_window = Gtk::ScrolledWindow.new
|
||
|
scrolled_window.add(tree)
|
||
|
hbox.pack_start(scrolled_window, false, false, 0)
|
||
|
|
||
|
notebook = Gtk::Notebook.new
|
||
|
hbox.pack_start(notebook, true, true, 0)
|
||
|
|
||
|
notebook.append_page(create_text(@info_buffer, false),
|
||
|
Gtk::Label.new('_Info', true))
|
||
|
|
||
|
notebook.append_page(create_text(@source_buffer, true),
|
||
|
Gtk::Label.new('_Source', true))
|
||
|
|
||
|
@info_buffer.create_tag('title',
|
||
|
{'font' => 'Sans 18'})
|
||
|
|
||
|
@source_buffer.create_tag('comment',
|
||
|
{'foreground' => 'red'})
|
||
|
@source_buffer.create_tag('const',
|
||
|
{'foreground' => 'ForestGreen'})
|
||
|
@source_buffer.create_tag('string',
|
||
|
{
|
||
|
'foreground' => 'RosyBrown',
|
||
|
'weight' => Pango::FontDescription::WEIGHT_BOLD
|
||
|
})
|
||
|
@source_buffer.create_tag('reserved',
|
||
|
{'foreground' => 'purple'})
|
||
|
end
|
||
|
|
||
|
def script_info(path)
|
||
|
title = nil
|
||
|
klass = nil
|
||
|
depend = nil
|
||
|
|
||
|
file = File.open(path)
|
||
|
file.each do |ln|
|
||
|
if not title and ln =~ /^=\s+(.*)$/
|
||
|
title = $1
|
||
|
if title =~ /^(.*)\((.+?)\)$/
|
||
|
title = $1
|
||
|
depend = $2
|
||
|
end
|
||
|
elsif not klass and ln =~ /\s*class\s+([A-Z][A-Za-z0-9_]*)/
|
||
|
klass = $1
|
||
|
end
|
||
|
|
||
|
if title and klass
|
||
|
break
|
||
|
end
|
||
|
end
|
||
|
|
||
|
if not klass
|
||
|
raise "File not found: #{path}. This script should be run from its directory."
|
||
|
end
|
||
|
|
||
|
return title, klass.intern, depend
|
||
|
end
|
||
|
|
||
|
def generate_index
|
||
|
# Target scripts
|
||
|
scripts = Dir.glob('*.rb') - %w(common.rb main.rb)
|
||
|
|
||
|
# Generate index tree
|
||
|
children = {}
|
||
|
index = []
|
||
|
|
||
|
scripts.each do |fn|
|
||
|
title, klass, depend = script_info(fn)
|
||
|
|
||
|
if depend and not Gtk.const_defined?(depend)
|
||
|
next
|
||
|
end
|
||
|
|
||
|
if title =~ %r{^(.+?)/(.+)$}
|
||
|
parent = $1
|
||
|
child = $2
|
||
|
|
||
|
unless children[parent]
|
||
|
children[parent] = []
|
||
|
index += [[parent, nil, nil, []]]
|
||
|
end
|
||
|
|
||
|
children[parent] += [[child, fn, klass]]
|
||
|
else
|
||
|
index += [[title, fn, klass]]
|
||
|
end
|
||
|
end
|
||
|
|
||
|
# Sort children
|
||
|
children.each_key do |parent|
|
||
|
children[parent].sort! do |a, b|
|
||
|
a[0] <=> b[0]
|
||
|
end
|
||
|
end
|
||
|
|
||
|
# Expand children
|
||
|
index.collect! do |row|
|
||
|
if row[3]
|
||
|
row[3] = children[row[0]]
|
||
|
end
|
||
|
|
||
|
row
|
||
|
end
|
||
|
|
||
|
index.sort! do |a, b|
|
||
|
a[0] <=> b[0]
|
||
|
end
|
||
|
|
||
|
index
|
||
|
end
|
||
|
|
||
|
def create_tree
|
||
|
model = Gtk::TreeStore.new(String, String, String, TrueClass)
|
||
|
|
||
|
tree_view = Gtk::TreeView.new
|
||
|
tree_view.set_model(model)
|
||
|
selection = tree_view.selection
|
||
|
|
||
|
selection.set_mode(Gtk::SELECTION_BROWSE)
|
||
|
tree_view.set_size_request(200, -1)
|
||
|
|
||
|
append_children(model, generate_index)
|
||
|
|
||
|
cell = Gtk::CellRendererText.new
|
||
|
cell.style = Pango::FontDescription::STYLE_ITALIC
|
||
|
column = Gtk::TreeViewColumn.new("Widget (double click for demo)", cell,
|
||
|
{
|
||
|
'text' => TITLE_COLUMN,
|
||
|
'style_set' => ITALIC_COLUMN,
|
||
|
})
|
||
|
|
||
|
tree_view.append_column(column)
|
||
|
|
||
|
selection.signal_connect('changed') do |selection|
|
||
|
iter = selection.selected
|
||
|
load_file(iter.get_value(FILENAME_COLUMN)) if iter
|
||
|
end
|
||
|
tree_view.signal_connect('row_activated') do |tree_view, path, column|
|
||
|
row_activated_cb(tree_view.model, path)
|
||
|
end
|
||
|
|
||
|
tree_view.expand_all
|
||
|
return tree_view
|
||
|
end
|
||
|
|
||
|
def append_children(model, source, parent = nil)
|
||
|
source.each do |title, filename, klass, children|
|
||
|
iter = model.append(parent)
|
||
|
|
||
|
[title, filename, klass].each_with_index do |value, i|
|
||
|
if value
|
||
|
iter.set_value(i, value)
|
||
|
end
|
||
|
end
|
||
|
iter.set_value(ITALIC_COLUMN, false)
|
||
|
|
||
|
if children
|
||
|
append_children(model, children, iter)
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
def row_activated_cb(model, path)
|
||
|
iter = model.get_iter(path)
|
||
|
iter.set_value(ITALIC_COLUMN, true)
|
||
|
|
||
|
lib = iter.get_value(FILENAME_COLUMN)
|
||
|
if lib
|
||
|
require(lib)
|
||
|
|
||
|
klass = Demo.const_get(iter.get_value(CLASS_COLUMN))
|
||
|
window = klass.new
|
||
|
window.signal_connect('destroy') do
|
||
|
iter.set_value(ITALIC_COLUMN, false)
|
||
|
end
|
||
|
window.show_all
|
||
|
@window = window
|
||
|
end
|
||
|
end
|
||
|
|
||
|
def create_text(buffer, is_source)
|
||
|
scrolled_window = Gtk::ScrolledWindow.new
|
||
|
scrolled_window.set_policy(Gtk::POLICY_AUTOMATIC,
|
||
|
Gtk::POLICY_AUTOMATIC)
|
||
|
scrolled_window.set_shadow_type(Gtk::SHADOW_IN)
|
||
|
|
||
|
text_view = Gtk::TextView.new
|
||
|
|
||
|
text_view.set_buffer(buffer)
|
||
|
text_view.set_editable(false)
|
||
|
text_view.set_cursor_visible(false)
|
||
|
|
||
|
scrolled_window.add(text_view)
|
||
|
|
||
|
if is_source
|
||
|
font_desc = Pango::FontDescription.new('Monospace 12')
|
||
|
text_view.modify_font(font_desc)
|
||
|
|
||
|
text_view.set_wrap_mode(Gtk::TextTag::WRAP_NONE)
|
||
|
else
|
||
|
text_view.set_wrap_mode(Gtk::TextTag::WRAP_WORD)
|
||
|
text_view.set_pixels_above_lines(2)
|
||
|
text_view.set_pixels_below_lines(2)
|
||
|
end
|
||
|
|
||
|
return scrolled_window
|
||
|
end
|
||
|
|
||
|
def fontify(start_iter = @source_buffer.start_iter,
|
||
|
end_iter = @source_buffer.end_iter)
|
||
|
str = @source_buffer.get_text(start_iter, end_iter, true)
|
||
|
|
||
|
tokenizer = RubyTokonizer.new
|
||
|
tokenizer.tokenize(str, start_iter.offset) do |tag, start, last|
|
||
|
@source_buffer.apply_tag(tag.to_s,
|
||
|
@source_buffer.get_iter_at_offset(start),
|
||
|
@source_buffer.get_iter_at_offset(last))
|
||
|
end
|
||
|
end
|
||
|
|
||
|
|
||
|
def load_file(filename)
|
||
|
if filename == @current_file
|
||
|
return
|
||
|
end
|
||
|
|
||
|
@info_buffer.delete(*@info_buffer.bounds)
|
||
|
|
||
|
@source_buffer.delete(*@source_buffer.bounds)
|
||
|
|
||
|
file = begin
|
||
|
File.open(filename)
|
||
|
rescue
|
||
|
$stderr.puts "Cannot open: #{$!}" if $DEBUG
|
||
|
return
|
||
|
end
|
||
|
start = @info_buffer.get_iter_at_offset(0)
|
||
|
state = :before_header
|
||
|
|
||
|
file.each do |line|
|
||
|
case state
|
||
|
when :before_header
|
||
|
if line =~ /^=begin$/
|
||
|
state = :in_header
|
||
|
end
|
||
|
when :in_header
|
||
|
if line =~ /^=end$/
|
||
|
state = :body
|
||
|
start = @source_buffer.get_iter_at_offset(0)
|
||
|
elsif line =~ /^=\s+(.*)$/
|
||
|
title = $1
|
||
|
title.gsub!(/\s*\(.*\)$/, '') # Delete depend field
|
||
|
|
||
|
last = start
|
||
|
|
||
|
@info_buffer.insert(last, title)
|
||
|
start = last.clone
|
||
|
|
||
|
start.backward_chars(title.length)
|
||
|
@info_buffer.apply_tag('title', start, last)
|
||
|
|
||
|
start = last
|
||
|
else
|
||
|
@info_buffer.insert(start, line)
|
||
|
end
|
||
|
when :body # Reading program body
|
||
|
@source_buffer.insert(start, line)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
fontify
|
||
|
|
||
|
@current_file = filename
|
||
|
end
|
||
|
end
|
||
|
|
||
|
class RubyTokonizer
|
||
|
RESERVED_WORDS = %w(begin end module class def if then else while unless do case when require yield)
|
||
|
RESERVED_WORDS_PATTERN = Regexp.compile(/(^|\s+)(#{RESERVED_WORDS.collect do |pat| Regexp.quote(pat) end.join('|')})(\s+|$)/)
|
||
|
|
||
|
def tokenize(str, index = 0)
|
||
|
until str.empty?
|
||
|
tag = nil
|
||
|
|
||
|
case str
|
||
|
when /".+?"/, /'.+?'/
|
||
|
tag = :string
|
||
|
when /#.*$/
|
||
|
tag = :comment
|
||
|
when RESERVED_WORDS_PATTERN
|
||
|
tag = :reserved
|
||
|
when /[A-Z][A-Za-z0-9_]+/
|
||
|
tag = :const
|
||
|
end
|
||
|
|
||
|
if tag
|
||
|
tokenize($~.pre_match, index) do |*args|
|
||
|
yield(*args)
|
||
|
end
|
||
|
yield(tag, index + $~.begin(0), index + $~.end(0))
|
||
|
index += (str.length - $~.post_match.length)
|
||
|
str = $~.post_match
|
||
|
else
|
||
|
index += str.length
|
||
|
str = ''
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
|
||
|
target = ARGV.shift
|
||
|
if target
|
||
|
Demo::INDEX.each do |title, filename, klass_symbol, children|
|
||
|
if target == filename or target == klass_symbol.id2name
|
||
|
require filename
|
||
|
|
||
|
window = Demo.const_get(klass_symbol).new
|
||
|
window.show_all
|
||
|
|
||
|
class << window
|
||
|
def quit
|
||
|
Gtk.main_quit
|
||
|
end
|
||
|
end
|
||
|
|
||
|
break
|
||
|
end
|
||
|
end
|
||
|
else
|
||
|
main = Demo::Main.new
|
||
|
main.set_default_size(600, 400)
|
||
|
main.show_all
|
||
|
end
|
||
|
|
||
|
Gtk.main
|