diff options
author | Rasmus Steinke <rasi@xssn.at> | 2012-08-11 02:40:34 +0200 |
---|---|---|
committer | Rasmus Steinke <rasi@xssn.at> | 2012-08-11 02:40:34 +0200 |
commit | f140a1642ebfde198946ad6760c1003c1cb9a8c3 (patch) | |
tree | 7538aa90092f99c661ec3abb4a69944767315dc5 /bin/subtle-contrib/ruby | |
parent | e3fd45703267ce67d8b1e1fb53a4a1b4ccda551e (diff) | |
download | dotfiles-f140a1642ebfde198946ad6760c1003c1cb9a8c3.tar.gz dotfiles-f140a1642ebfde198946ad6760c1003c1cb9a8c3.tar.xz |
scripts
Diffstat (limited to 'bin/subtle-contrib/ruby')
-rw-r--r-- | bin/subtle-contrib/ruby/graviton.rb | 481 | ||||
-rw-r--r-- | bin/subtle-contrib/ruby/launcher.rb | 632 | ||||
-rw-r--r-- | bin/subtle-contrib/ruby/levenshtein.rb | 74 | ||||
-rw-r--r-- | bin/subtle-contrib/ruby/merger.rb | 292 | ||||
-rw-r--r-- | bin/subtle-contrib/ruby/positioner.rb | 300 | ||||
-rw-r--r-- | bin/subtle-contrib/ruby/selector.rb | 344 | ||||
-rw-r--r-- | bin/subtle-contrib/ruby/styler.rb | 859 | ||||
-rw-r--r-- | bin/subtle-contrib/ruby/termstyler.rb | 289 | ||||
-rw-r--r-- | bin/subtle-contrib/ruby/vitag.rb | 127 |
9 files changed, 3398 insertions, 0 deletions
diff --git a/bin/subtle-contrib/ruby/graviton.rb b/bin/subtle-contrib/ruby/graviton.rb new file mode 100644 index 0000000..09d8a16 --- /dev/null +++ b/bin/subtle-contrib/ruby/graviton.rb @@ -0,0 +1,481 @@ +#!/usr/bin/ruby +# +# @file Graviton +# +# @copyright (c) 2010-2011, Christoph Kappel <unexist@dorfelite.net> +# @version $Id$ +# +# This program can be distributed under the terms of the GNU GPLv2. +# See the file COPYING for details. +# +# Graviton is a helper to create custom gravities +# +# http://subforge.org/projects/subtle-contrib/wiki/Graviton +# + +begin + require "subtle/subtlext" +rescue LoadError + puts ">>> ERROR: Couldn't find subtlext" + exit +end + +# Check for subtlext version +major, minor, teeny = Subtlext::VERSION.split(".").map(&:to_i) +if 0 == major and 9 == minor and 2829 > teeny + puts ">>> ERROR: launcher needs at least subtle `0.9.2829' (found: %s)" % [ + Subtlext::VERSION + ] + exit +end + +begin + require "gtk2" +rescue LoadError + puts <<EOF +>>> ERROR: Couldn't find the gem `gtk2' +>>> Please install it with following command: +>>> gem install gtk2 +EOF + exit +end + +# Styler class +module Subtle # {{{ + module Contrib # {{{ + # Colors + COLORS = { + :cyan => "#00FFFF", + :green => "#008000", + :olive => "#808000", + :teal => "#008080", + :blue => "#0000FF", + :silver => "#C0C0C0", + :lime => "#00FF00", + :navy => "#000080", + :purple => "#800080", + :magenta => "#FF00FF", + :maroon => "#800000", + :red => "#FF0000", + :yellow => "#FFFF00", + :gray => "#808080" + } + + # Rectangle class + class Rectangle # {{{ + attr_accessor :x, :y, :width, :height, :color + + ## initialize {{{ + # Create a new rectangle + # @param [Fixnum] x X position + # @param [Fixnum] y Y position + # @param [Fixnum] width Rectangle width + # @param [Fixnum] height Rectangle height + ## + + def initialize(x, y, width, height, color) + @x = x + @y = y + @width = width + @height = height + @color = color + end # }}} + + ## normalize # {{{ + # Normalize width/height and set left top border to origin + ## + + def normalize + if 0 > @width + @width *= -1 + @x -= @width + end + + if 0 > @height + @height *= -1 + @y -= @height + end + end # }}} + + ## is_edge? {{{ + # Check if x/y is edge of rectangle + # @param [Fixnum] x X position + # @param [Fixnum] y Y position + # @return [Symbol, nil] Selected edge or nil + ## + + def is_edge?(x, y) + if x == @x and y == @y + :top_left + elsif x == (@x + @width) and y == @y + :top_right + elsif x == @x and y == (@y + @height) + :bottom_left + elsif x == (@x + @width) and y == (@y + @height) + :bottom_right + else + nil + end + end # }}} + + ## to_gravity {{{ + # Calculate gravity from rectangle values for given width/height + # @param [Fixnum] width Width for calculation + # @param [Fixnum] height Height for calculation + + def to_gravity(width, height) + x = ratio(@x, width) + y = ratio(@y, height) + w = ratio(@width, width) + h = ratio(@height, height) + + "gravity :%s, [ %d, %d, %d, %d ]" % [ @color, x, y, w, h ] + end # }}} + + ## to_s {{{ + # Convert rectable to string + ## + + def to_s + "x=%d, y=%d, width=%d, height=%d" % [ @x, @y, @width, @height ] + end # }}} + + private + + def ratio(value, ratio) # {{{ + (value.to_f * 100.0 / ratio).to_i + end # }}} + end # }}} + + # Graviton class + class Graviton < Gtk::Window # {{{ + + ## initialize {{{ + # Init window + ## + + def initialize + super + + # Init sizes + @geom = Subtlext::Screen.current.geometry + @gridsize = 20 + @panel_x = 7 + @panel_y = 7 + @panel_width = @geom.width / 3 + @panel_height = @geom.height / 3 + + # Shrink grid + @panel_width -= @panel_width % @gridsize + @panel_height -= @panel_height % @gridsize + + @grid_x = @panel_width / @gridsize + @grid_y = @panel_height / @gridsize + + @window_width = @panel_width + 14 + @window_height = @panel_height + 44 + + # Variables + @rectangles = [] + @cur_rect = nil + @cur_edge = nil + @sx = 0 + @sy = 0 + @x = 0 + @y = 0 + + # Options + set_title("Graviton for subtle #{Subtlext::VERSION}") + set_wmclass("graviton", "subtle") + set_resizable(false) + set_keep_above(true) + set_size_request(@window_width, @window_height) + set_window_position(Gtk::Window::POS_CENTER) + stick + + # Signals + signal_connect("delete_event") do + false + end + + signal_connect("destroy") do + Gtk.main_quit + end + + # Alignment + align = Gtk::Alignment.new(0.5, 0.5, 0.5, 0.5) + align.set_padding(7, 7, 7, 7) + + add(align) + + # Vbox + vbox = Gtk::VBox.new + align.add(vbox) + + # Frame + frame = Gtk::Frame.new + frame.set_border_width(0) + frame.set_shadow_type(Gtk::SHADOW_NONE) + vbox.pack_start(frame) + + # Area + @area = Gtk::DrawingArea.new + @area.set_size_request(@panel_width, @panel_height) + @area.add_events( + Gdk::Event::POINTER_MOTION_MASK| + Gdk::Event::BUTTON1_MOTION_MASK| + Gdk::Event::BUTTON_PRESS_MASK| + Gdk::Event::BUTTON_RELEASE_MASK| + Gdk::Event::POINTER_MOTION_HINT_MASK + ) + frame.add(@area) + + # Create context and surface + @surface = Cairo::ImageSurface.new(Cairo::FORMAT_ARGB32, + @panel_width, @panel_width + ) + @ctxt = Cairo::Context.new(@surface) + + # Area events + @area.signal_connect("expose_event") do |*args| + expose_event(*args) + end + + @area.signal_connect("motion_notify_event") do |*args| + motion_event(*args) + end + + @area.signal_connect("button_press_event") do |*args| + press_event(*args) + end + + @area.signal_connect("button_release_event") do |*args| + release_event(*args) + end + + # Hbox + hbox = Gtk::HButtonBox.new + vbox.pack_start(hbox, false, false, 2) + + # Print button + button = Gtk::Button.new("Print") + hbox.pack_start(button) + + button.signal_connect("clicked") do |*args| + button_print(*args) + end + + # Reset button + button = Gtk::Button.new("Reset") + hbox.pack_start(button) + + button.signal_connect("clicked") do + @rectangles = [] + @area.signal_emit("expose_event", nil) + end + + # Exit button + button = Gtk::Button.new("Exit") + hbox.pack_start(button) + + button.signal_connect("clicked") do + Gtk.main_quit + end + + show_all + end # }}} + + private + + def expose_event(widget, event) # {{{ + # Clear + @ctxt.set_source_rgba(0.0, 0.0, 0.0, 1.0) + @ctxt.set_operator(Cairo::OPERATOR_SOURCE) + @ctxt.paint + + # Grid + @ctxt.set_line_width(1) + @ctxt.set_source_rgba(0.6, 0.6, 0.6, 0.4) + + # Vertical lines + (0..@panel_width).step(@grid_x) do |x| + @ctxt.move_to(x, 0) + @ctxt.line_to(x, @panel_height) + end + + # Horizontal lines + (0..@panel_height).step(@grid_y) do |y| + @ctxt.move_to(0, y) + @ctxt.line_to(@panel_width, y) + end + @ctxt.stroke + + # Center + @ctxt.set_source_rgba(0.6, 0.0, 0.0, 0.4) + x = @grid_x * (@gridsize / 2) + y = @grid_y * (@gridsize / 2) + + # Vertical line + @ctxt.move_to(x, 0) + @ctxt.line_to(x, @panel_height) + + # Horizontal line + @ctxt.move_to(0, y) + @ctxt.line_to(@panel_width, y) + @ctxt.stroke + + # Rectangles + @ctxt.set_line_width(1.0) + @rectangles.each do |r| + #@ctxt.set_dash((@cur_rect == r ? 2 : 0)) + @ctxt.set_source_color(Gdk::Color.parse(COLORS[r.color])) + @ctxt.rectangle(r.x, r.y, r.width, r.height) + + # Cross + @ctxt.move_to(r.x, r.y) + @ctxt.line_to(r.x + r.width, r.y + r.height) + @ctxt.move_to(r.x + r.width, r.y) + @ctxt.line_to(r.x, r.y + r.height) + @ctxt.stroke + end + + # Position + @ctxt.move_to(@x, @y) + @ctxt.set_source_color(Gdk::Color.parse("#ff0000")) + @ctxt.rectangle(@x - 2, @y - 2, 4, 4) + @ctxt.stroke + + # Swap context + ctxt = widget.window.create_cairo_context + ctxt.set_source(@surface) + ctxt.paint + end # }}} + + def motion_event(widget, event) # {{{ + # Snap to closest grid knot + modx = event.x % @grid_x + mody = event.y % @grid_y + + @x = @grid_x / 2 < modx ? event.x - modx + @grid_x : event.x - modx + @y = @grid_y / 2 < mody ? event.y - mody + @grid_y : event.y - mody + + # Calculate new width/height + unless @cur_rect.nil? + case @cur_edge + when :top_left + @cur_rect.x = @x + @cur_rect.y = @y + @cur_rect.width = @sx - @x + @cur_rect.height = @sy - @y + when :top_right + @cur_rect.x = @sx + @cur_rect.y = @y + @cur_rect.width = @x - @sx + @cur_rect.height = @sy - @y + when :bottom_left + @cur_rect.x = @x + @cur_rect.y = @sy + @cur_rect.width = @sx - @x + @cur_rect.height = @y - @sy + when :bottom_right + @cur_rect.x = @sx + @cur_rect.y = @sy + @cur_rect.width = @x - @sx + @cur_rect.height = @y - @sy + end + end + + expose_event(widget, event) + end # }}} + + def press_event(widget, event) # {{{ + if 1 == event.button + # Find rectangles by edge + @rectangles.each do |r| + case r.is_edge?(@x, @y) + when :top_left + @cur_rect = r + @cur_edge = :top_left + @sx = r.x + r.width + @sy = r.y + r.height + + return + when :top_right + @cur_rect = r + @cur_edge = :top_right + @sx = r.x + @sy = r.y + r.height + + return + when :bottom_left + @cur_rect = r + @cur_edge = :bottom_left + @sx = r.x + r.width + @sy = r.y + + return + when :bottom_right + @cur_rect = r + @cur_edge = :bottom_right + @sx = r.x + @sy = r.y + + return + else + end + end + + if @rectangles.size < COLORS.size + # Create new rectangle + @mode = :resize + @cur_edge = :bottom_right + @cur_rect = Rectangle.new( + @x, @y, 20, 20, + COLORS.keys[@rectangles.size] + ) + @rectangles << @cur_rect + + @sx = @x + @sy = @y + @x += @gridsize + @y += @gridsize + + expose_event(widget, event) + else + puts ">>> ERROR: Out of colors" + end + end + end # }}} + + def release_event(widget, event) # {{{ + if 1 == event.button + @cur_rect.normalize unless @cur_rect.nil? + @cur_rect = nil + @cur_edge = nil + @sx = 0 + @sy = 0 + end + end # }}} + + def button_print(widget) # {{{ + puts + + # Print rectangles + @rectangles.each do |r| + puts r.to_gravity(@panel_width, @panel_height) + end + + puts + end # }}} + end # }}} + end # }}} +end # }}} + +# Implicitly run< +if __FILE__ == $0 + Gtk.init + Subtle::Contrib::Graviton.new + Gtk.main +end + +# vim:ts=2:bs=2:sw=2:et:fdm=marker diff --git a/bin/subtle-contrib/ruby/launcher.rb b/bin/subtle-contrib/ruby/launcher.rb new file mode 100644 index 0000000..453f36a --- /dev/null +++ b/bin/subtle-contrib/ruby/launcher.rb @@ -0,0 +1,632 @@ +#!/usr/bin/ruby +# +# @file Launcher +# +# @copyright (c) 2010-2012, Christoph Kappel <unexist@dorfelite.net> +# @version $Id$ +# +# This program can be distributed under the terms of the GNU GPLv2. +# See the file COPYING for details. +# +# Launcher that combines modes/tagging of subtle and a browser search bar. +# +# It opens uris with your default browser via xdg-open. Easiest way to set +# it is to define $BROWSER in your shell rc files. +# +# Thanks, fauno, for your initial work! +# +# Examples: +# +# :urxvt - Call methods defined in the config +# g subtle wm - Change to browser view and search for 'subtle wm' via Google +# urxvt @editor - Open urxvt on view @editor with random tag +# urxvt @editor #work - Open urxvt on view @editor with tag #work +# urxvt #work - Open urxvt and tag with tag #work +# urxvt -urgentOnBell - Open urxvt with the urgentOnBell option +# +urxvt - Open urxvt and set full mode +# ^urxvt - Open urxvt and set floating mode +# *urxvt - Open urxvt and set sticky mode +# =urxvt - Open urxvt and set zaphod mode +# urx<Tab> - Open urxvt (tab completion) +# +# Keys: +# +# Up/Down - Cycle through history (per runtime) +# ESC - 1) Hide/exit launcher 2) stop reverse history search +# Enter - Run command +# ^R - Reverse history search +# +# http://subforge.org/projects/subtle-contrib/wiki/Launcher +# + +require 'singleton' +require 'uri' + +begin + require 'subtle/subtlext' +rescue LoadError + puts ">>> ERROR: Couldn't find subtlext" + exit +end + +# Check for subtlext version +major, minor, teeny = Subtlext::VERSION.split('.').map(&:to_i) +if major == 0 and minor == 10 and 3203 > teeny + puts ">>> ERROR: launcher needs at least subtle `0.10.3203' (found: %s)" % [ + Subtlext::VERSION + ] + exit +end + +begin + require_relative 'levenshtein.rb' +rescue LoadError => err + puts ">>> ERROR: Couldn't find `levenshtein.rb'" + exit +end + +# Launcher class +module Subtle # {{{ + module Contrib # {{{ + # Precompile regexps + RE_COMMAND = Regexp.new(/^([+\^\*]*[A-Za-z0-9_\-\/''\s]+)(\s[@#][A-Za-z0-9_-]+)*$/) + RE_MODES = Regexp.new(/^([+\^\*]*)([A-Za-z0-9_\-\/''\s]+)/) + RE_SEARCH = Regexp.new(/^[gs]\s+(.*)/) + RE_METHOD = Regexp.new(/^[:]\s*(.*)/) + RE_URI = Regexp.new(/^(http|https):\/\/[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,5}(([0-9]{1,5})?\/.*)?$/ix) + RE_BROWSER = Regexp.new(/(chrom[e|ium]|iron|navigator|firefox|opera)/i) + + # For history search + CTRL_R = "\x12".to_sym + + # Launcher class + class Launcher # {{{ + include Singleton + + # Default values + @@font_big = '-*-*-*-*-*-*-40-*-*-*-*-*-*-*' + @@font_small = '-*-*-*-*-*-*-14-*-*-*-*-*-*-*' + @@paths = '/usr/bin' + @@screen_num = 0 + + # Singleton methods + + ## fonts {{{ + # Set font strings + # @param [Array] fonts Fonts array + ## + + def self.fonts=(fonts) + if fonts.is_a?(Array) + @@font_big = fonts.first if(1 <= fonts.size) + @@font_small = fonts.last if(2 <= fonts.size) + end + end # }}} + + ## paths {{{ + # Set launcher path separated by colons + # @param [String, Array] paths Path list separated by colon or array + ## + + def self.paths=(paths) + if paths.is_a?(String) + @@paths = paths + elsif paths.is_a?(Array) + @@paths = paths.join(':') + end + end # }}} + + ## browser_screen_num {{{ + # Set screen num to show browser view + # @param [Fixnum] num Screen number + ## + + def self.browser_screen_num=(num) + @screen_num = num if num.is_a?(Fixnum) + end # }}} + + ## run {{{ + # Run the launcher + ## + + def self.run + self.instance.run + end # }}} + + # Instance methods + + ## initialize {{{ + # Create launcher instance + ## + + def initialize + @candidate = nil + @browser = nil + @view = nil + @x = 0 + @y = 0 + @width = 0 + @height = 0 + @completed = nil + @reverse = nil + + # Buffer + @buf_input = '' + @buf_info = '' + + # Parsed data + @parsed_tags = [] + @parsed_views = [] + @parsed_app = '' + @parsed_modes = '' + + # Cached data + @cached_tags = Subtlext::Tag.all.map(&:name) + @cached_views = Subtlext::View.all.map(&:name) + @cached_apps = {} + @cached_history = [] + + # FIXME: Find config instance + if defined?(Subtle::Config) + ObjectSpace.each_object(Subtle::Config) do |c| + @cached_sender = c + @cached_methods = c.methods(false).map(&:to_s) + end + end + + # Something close to a skiplist + @@paths.split(':').each do |path| + if Dir.exist?(path) + Dir.foreach(File.expand_path(path)) do |entry| + file = File.basename(entry) + sym = file[0].to_sym + + # Sort in + if @cached_apps.has_key?(sym) + @cached_apps[sym] << file + else + @cached_apps[sym] = [ file ] + end + end + else + puts ">>> ERROR: Skipping non-existing path `%s'" % [ path ] + end + end + + # Init for performance + @array1 = Array.new(20, 0) + @array2 = Array.new(20, 0) + + # Get colors + colors = Subtlext::Subtle.colors + + # Create input window + @input = Subtlext::Window.new(:x => 0, :y => 0, + :width => 1, :height => 1) do |w| + w.name = 'Launcher: Input' + w.font = @@font_big + w.foreground = colors[:focus_fg] + w.background = colors[:focus_bg] + w.border_size = 0 + end + + # Get font height and y offset of input window + @font_height1 = @input.font_height + 6 + @font_y1 = @input.font_y + + # Key down and redraw wrappers + @input.on :key_down do |key, mods| + begin + Launcher.instance.key_down(key, mods) + rescue => err + puts err, err.backtrace + end + end + + @input.on :redraw do + begin + Launcher.instance.redraw + rescue => err + puts err, err.backtrace + end + end + + # Create info window + @info = Subtlext::Window.new(:x => 0, :y => 0, + :width => 1, :height => 1) do |w| + w.name = 'Launcher: Info' + w.font = @@font_small + w.foreground = colors[:stipple] + w.background = colors.has_key?(:panel_top) ? + colors[:panel_top] : colors[:panel] + w.border_size = 0 + end + + # Get font height and y offset of info window + @font_height2 = @info.font_height + 6 + @font_y2 = @info.font_y + end # }}} + + ## key_down {{{ + # Key down handler + # @param [String] key Input key + # @param [Array] mods Modifier list + ## + + def key_down(key, mods) + ret = true + + # Handle keys + case key + when :up, :down # {{{ + idx = (@cached_history.index(@buf_input) + + (:up == key ? -1 : 1)) rescue -1 + @buf_input = @cached_history[idx] || "" # }}} + when :tab # {{{ + complete # }}} + when :escape # {{{ + # Stop reverse-search with ESC + if @reverse + @reverse = nil + else + @buf_input = "" + @buf_info = "" + @reverse = nil + @candidate = nil + ret = false + end # }}} + when :backspace # {{{ + if @reverse + @reverse.chop! + + reverse_complete + else + @buf_input.chop! + end # }}} + when :space # {{{ + @buf_input << " " # }}} + when :return # {{{ + @cached_history << @buf_input + @buf_input = "" + @buf_info = "" + @reverse = nil + ret = false # }}} + when CTRL_R # {{{ + if mods.is_a?(Array) and mods.include?(:control) + @reverse = "" + + reverse_complete + else + @buf_input << key.to_s + end + else + if @reverse + @reverse << key.to_s + + reverse_complete + else + @buf_input << key.to_s + end # }}} + end + + # Reset completed buffer + @completed = nil unless :tab == key + + parse + + ret + end # }}} + + ## redraw {{{ + # Redraw window contents + ## + + def redraw + # Fill input window + @input.clear + @input.draw_text(3, @font_y1 + 3, @buf_input) unless @buf_input.empty? + + # Assemble info string + str = @buf_info.empty? ? 'Ready..' : @buf_info + str << ", reverse-search: " + @reverse if @reverse + + # Fill info window + @info.clear + @info.draw_text(3, @font_y2 + 3, str) + end # }}} + + ## move {{{ + # Move launcher windows to current screen + ## + + def move + # Geometry + geo = Subtlext::Screen.current.geometry + @width = geo.width * 80 / 100 + @x = geo.x + ((geo.width - @width) / 2) + @y = geo.y + geo.height - @font_height1 - @font_height2 - 40 + + @input.geometry = [ @x, @y, @width, @font_height1 ] + @info.geometry = [ @x, @y + @font_height1, @width, @font_height2 ] + end # }}} + + ## show {{{ + # Show launcher + ## + + def show + move + + # Show info first because input blocks + @info.show + @input.show + end # }}} + + ## hide # {{{ + # Hide launcher + ## + + def hide + @input.hide + @info.hide + end # }}} + + ## run {{{ + # Show and run launcher + ## + + def run + show + hide + + # Check if we have a candidate + case @candidate + when Symbol #{{{ + @cached_sender.send(@candidate) # }}} + when String # {{{ + # Find or create tags + @parsed_tags.map! do |t| + tag = Subtlext::Tag.first(t) || Subtlext::Tag.new(t) + tag.save + + tag + end + + # Find or create view and add tag + @parsed_views.each do |v| + view = Subtlext::View.first(v) || Subtlext::View.new(v) + view.save + + view.tag(@parsed_tags) unless view.nil? or @parsed_tags.empty? + end + + # Spawn app, tag it and set modes + unless (client = Subtlext::Subtle.spawn(@parsed_app)).nil? + client.tags = @parsed_tags unless @parsed_tags.empty? + + # Set modes + unless @parsed_modes.empty? + flags = [] + + # Translate modes + @parsed_modes.each_char do |c| + case c + when '+' then flags << :full + when '^' then flags << :float + when '*' then flags << :stick + when '=' then flags << :zaphod + end + end + + client.flags = flags + end + end # }}} + when URI # {{{ + find_browser + + unless @browser.nil? + Subtlext::Screen[@@screen_num].view = @view + system("xdg-open '%s' &>/dev/null" % [ @candidate.to_s ]) + @browser.focus + end # }}} + end + + @candidate = nil + end # }}} + + private + + def reverse_complete # {{{ + # Handle reverse search + if @reverse and not @reverse.empty? and @cached_history.any? + matches = @cached_history.reverse.select { |h| h =~ /#{@reverse}/ } + @buf_input = matches.first || "" + end + end # }}} + + def complete # {{{ + guesses = [] + lookup = nil + + # Clear info field + if @buf_input.empty? or @buf_input.nil? + redraw + return + end + + # Store curret buffer + @completed = @buf_input if @completed.nil? + + # Select lookup cache + last = @completed.split(' ').last rescue @completed + case last[0] + when '#' + lookup = @cached_tags + prefix = '#' + when '@' + lookup = @cached_views + prefix = '@' + when ':' + lookup = @cached_methods + prefix = ':' + when '+', '^', '*' + lookup = @cached_apps[last[@parsed_modes.size].to_sym] + prefix = @parsed_modes + else + lookup = @cached_apps[last[0].to_sym] + prefix = '' + end + + # Collect guesses + unless lookup.nil? + lookup.each do |l| + guesses << [ + '%s%s' %[ prefix, l ], + Levenshtein::distance(last.gsub(/^[@#:]/, ''), + l, 1, 8, 5, @array1, @array2) + ] + end + + # Sort by distance and remove it afterwards + guesses.sort! { |a, b| a[1] <=> b[1] } + guesses.map! { |a| a.first } + + last = @buf_input.split(' ').last rescue @buf_input + idx = (guesses.index(last) + 1) % guesses.size rescue 0 + + @candidate = guesses[idx] + @buf_input.gsub!(/#{last}$/, guesses[idx]) + + # Convert to symbol if methods are guessed + @candidate = @candidate.delete(':').to_sym if ':' == prefix + end + rescue => err + puts err, err.backtrace + end # }}} + + def parse # {{{ + # Handle input + unless @buf_input.empty? or @buf_input.nil? + if RE_URI.match(@buf_input) + @candidate = URI.parse(@buf_input) + @buf_info = 'Goto %s' % [ @candidate.to_s ] + elsif RE_SEARCH.match(@buf_input) + @candidate = URI.parse( + 'http://www.google.com/#q=%s' % [ URI.escape($1) ] + ) + @buf_info = 'Goto %s' % [ @candidate.to_s ] + elsif RE_METHOD.match(@buf_input) + @candidate = $1.to_sym + @buf_info = 'Call :%s' % [ @candidate ] + elsif RE_COMMAND.match(@buf_input) + @candidate = @buf_input + @parsed_tags = [] + @parsed_views = [] + @parsed_app = '' + @parsed_modes = '' + + # Parse args + @candidate.split.each do |arg| + case arg[0] + when '#' then @parsed_tags << arg[1..-1] + when '@' then @parsed_views << arg[1..-1] + when '+', '^', '*' + app, @parsed_modes, @parsed_app = RE_MODES.match(arg).to_a + else + if @parsed_app.empty? + @parsed_app += arg + else + @parsed_app += ' ' + arg + end + end + end + + # Add an ad-hoc tag if we don't have any and need one + if @parsed_views.any? and not @parsed_app.empty? and + @parsed_tags.empty? + @parsed_tags << 'tag_%d' % [ rand(1337) ] + end + + if @parsed_views.any? + @buf_info = 'Launch %s%s on %s (via %s)' % [ + modes2text(@parsed_modes), + @parsed_app, + @parsed_views.join(', '), + @parsed_tags.join(', ') + ] + elsif @parsed_tags.any? + @buf_info = 'Launch %s%s (via %s)' % [ + modes2text(@parsed_modes), + @parsed_app, + @parsed_tags.join(', ') + ] + else + @buf_info = 'Launch %s%s' % [ + modes2text(@parsed_modes), @parsed_app + ] + end + end + else + @buf_info = "" + end + + redraw + end # }}} + + def modes2text(modes) # {{{ + ret = [] + + # Collect mode verbs + modes.each_char do |c| + case c + when '+' then ret << 'full' + when '^' then ret << 'floating' + when '*' then ret << 'sticky' + when '=' then ret << 'zaphod' + end + end + + ret.any? ? '%s ' % [ ret.join(', ') ] : '' + end # }}} + + def find_browser # {{{ + begin + if @browser.nil? + Subtlext::Client.all.each do |c| + if c.klass.match(RE_BROWSER) + @browser = c + @view = c.views.first + return + end + end + + puts '>>> ERROR: No supported browser found' + puts ' (Supported: Chrome, Firefox and Opera)' + end + rescue + @browser = nil + @view = nil + end + end # }}} + end # }}} + end # }}} +end # }}} + +# Implicitly run +if __FILE__ == $0 + # Set fonts + #Subtle::Contrib::Launcher.fonts = [ + # 'xft:DejaVu Sans Mono:pixelsize=80:antialias=true', + # 'xft:DejaVu Sans Mono:pixelsize=12:antialias=true' + #] + + # Set paths + # Subtle::Contrib::Launcher.paths = [ '/usr/bin', '~/bin' ] + + # Set browser screen + Subtle::Contrib::Launcher.browser_screen_num = 0 + + Subtle::Contrib::Launcher.run +end + +# vim:ts=2:bs=2:sw=2:et:fdm=marker diff --git a/bin/subtle-contrib/ruby/levenshtein.rb b/bin/subtle-contrib/ruby/levenshtein.rb new file mode 100644 index 0000000..6122149 --- /dev/null +++ b/bin/subtle-contrib/ruby/levenshtein.rb @@ -0,0 +1,74 @@ +# +# @file Calculate Levenshtein distance +# +# @copyright (c) 2010-2011, Christoph Kappel <unexist@dorfelite.net> +# @version $Id$ +# +# This program can be distributed under the terms of the GNU GPLv2. +# See the file COPYING for details. +# + +module Levenshtein + MAX_LENGTH = 255 + + ## distance {{{ + # @brief Calculate the Levenshtein Distance + # @param [String] s1 First string + # @param [String] s2 Second string + # @param [Fixnum] cost_ins Cost for insertion + # @param [Fixnum] cost_rep Cost for replace + # @param [Fixnum] cost_del Cost for deletion + # @param [Array] a1 First array + # @param [Array] a2 Second array + # @raise [String] Error + # @return [Fixnum] Calculated distance + ## + + def self.distance(s1, s2, cost_ins = 1, cost_rep = 1, + cost_del = 1, a1 = nil, a2 = nil) + # Step 1: Check string length + l1 = s1.length + l2 = s2.length + + # Check length + return l2 * cost_ins if 0 == l1 + return l1 * cost_del if 0 == l2 + + raise "Max length" if l1 > MAX_LENGTH || l2 > MAX_LENGTH + + # Step 2: Create and init arrays + p1 = a1 || Array.new(l2 + 1, 0) + p2 = a2 || Array.new(l2 + 1, 0) + + (0..l2).each { |i| p1[i] = i * cost_ins } + + # Step 3: Iterate over string s1 + (0..(l1 - 1)).each do |i| + p2[0] = p1[0] + cost_del + + # Step 4: Iterate over string s2 + (0..(l2 - 1)).each do |j| + # Step 5: Get cost + c0 = p1[j] + ((s1[i] == s2[j]) ? 0 : cost_rep) + c1 = p1[j + 1] + cost_del + c0 = c1 if c1 < c0 + + c2 = p2[j] + cost_ins + c0 = c2 if c2 < c0 + + # Step 6: Store min value in matrix + p2[j + 1] = c0 + end + + # Swap arrays + tmp = p1 + p1 = p2 + p2 = tmp + end + + # Step 7: Return distance + c0 = p1[l2] + + c0 + end # }}} +end # }}} diff --git a/bin/subtle-contrib/ruby/merger.rb b/bin/subtle-contrib/ruby/merger.rb new file mode 100644 index 0000000..30839ac --- /dev/null +++ b/bin/subtle-contrib/ruby/merger.rb @@ -0,0 +1,292 @@ +#!/usr/bin/ruby +# +# @file Merger +# +# @copyright (c) 2011, Christoph Kappel <unexist@dorfelite.net> +# @version $Id$ +# +# This program can be distributed under the terms of the GNU GPLv2. +# See the file COPYING for details. +# +# Select and tag/untag visible views of current client window +# +# Colors: +# +# Focus - Currently selected view +# View - Other views +# Occupied - Views client is visible +# Urgent - Selected views +# +# Keys: +# +# Left, Up - Move to left +# Right, Down - Move to right +# Escape - Hide/exit +# Space - Select view +# Return - Tag/untag selected views and exit hide/exit +# +# http://subforge.org/projects/subtle-contrib/wiki/Positioner +# + +require "singleton" + +begin + require "subtle/subtlext" +rescue LoadError + puts ">>> ERROR: Couldn't find subtlext" + exit +end + +# Check for subtlext version +major, minor, teeny = Subtlext::VERSION.split(".").map(&:to_i) +if(major == 0 and minor == 10 and 3006 > teeny) + puts ">>> ERROR: merger needs at least subtle `0.10.3006' (found: %s)" % [ + Subtlext::VERSION + ] + exit +end + +# Merger class +module Subtle # {{{ + module Contrib # {{{ + class Merger # {{{ + include Singleton + + # Default values + @@font = "-*-*-medium-*-*-*-14-*-*-*-*-*-*-*" + + # Singleton methods + + ## fonts {{{ + # Set font strings + # @param [String] fonts Fonts array + ## + + def self.font=(font) + @@font = font + end # }}} + + ## run {{{ + # Run expose + ## + + def self.run + self.instance.run + end # }}} + + # Instance methods + + ## initialize {{{ + # Create expose instance + ## + + def initialize + # Values + @colors = Subtlext::Subtle.colors + @merged = {} + @backup = {} + + # Create main window + @win = Subtlext::Window.new(:x => 0, :y => 0, :width => 1, :height => 1) do |w| + w.name = "Merger" + w.font = @@font + w.foreground = @colors[:title_fg] + w.background = @colors[:title_bg] + w.border_size = 0 + end + + # Font metrics + @font_height = @win.font_height + 6 + @font_y = @win.font_y + + # Handler + @win.on :key_down, method(:key_down) + @win.on :draw, method(:redraw) + end # }}} + + ## run {{{ + # Show and run positioner + ## + + def run + update + show + hide + end # }}} + + private + + ## key_down {{{ + # Key down handler + # @param [String] key Pressed key + ## + + def key_down(key) + ret = true + + case key + when :left, :up # {{{ + idx = @views.index(@selected) + idx -= 1 if(1 < idx) + @selected = @views[idx] # }}} + when :right, :down # {{{ + idx = @views.index(@selected) + idx += 1 if(idx < (@views.size - 1)) + @selected = @views[idx] # }}} + when :space # {{{ + if(@merged[@current.name].include?(@selected)) + @merged[@current.name].delete(@selected) + else + @merged[@current.name] << @selected + end # }}} + + p @merged + when :return # {{{ + # Restore tags or update + if(@merged[@current.name].empty?) + @merged.delete(@current.name) + @current.tags = @backup[@current.name] + else + @current.tags = @merged[@current.name].inject( + @backup[@current.name]) { |r, v| r | v.tags } + end + + ret = false # }}} + when :escape # {{{ + @merged.delete(@current.name) + ret = false # }}} + end + + redraw(@win) if(ret) + + ret + end # }}} + + ## update # {{{ + # Update clients and windows + ## + + def update + @current = Subtlext::View.current + @views = Subtlext::View.all.select { |v| v != @current } + @selected = @views.first + + @views.unshift(@current) + + # Backup tags of current view + unless(@backup.keys.include?(@current.name)) + @backup[@current.name] = @current.tags + end + + # Create empty array + unless(@merged.keys.include?(@current.name)) + @merged[@current.name] = [] + end + + arrange + end # }}} + + ## arrange {{{ + # Arrange window and subwindows + ## + + def arrange + geo = Subtlext::Screen.current.geometry + width = geo.width * 50 / 100 #< Max width + height = @font_height + wx = 0 + wy = 0 + len = 0 + wwidth = 0 + + # Arrange client windows + @views.each_with_index do |v, i| + len = @win.font_width(v.name) + 6 + + # Wrap lines + if(wx + len > width) + wwidth = wx if(wx > wwidth) + wx = 0 + wy += @font_height + end + + wx += len + end + + # Update geometry + width = 0 == wwidth ? wx : wwidth + height += wy + x = geo.x + ((geo.width - width) / 2) + y = geo.y + ((geo.height - height) / 2) + + @win.geometry = [ x , y, width, height ] + end # }}} + + ## redraw {{{ + # Redraw window content + # @param [Window] w Window instance + ## + + def redraw(w) + @win.clear + + wx = 0 + wy = 0 + len = 0 + + @views.each_with_index do |v, i| + len = @win.font_width(v.name) + 6 + + # Select color + if(v == @selected) + fg = @colors[:focus_fg] + bg = @colors[:focus_bg] + elsif(v == @current) + fg = @colors[:occupied_fg] + bg = @colors[:occupied_bg] + else + fg = @colors[:unoccupied_fg] + bg = @colors[:unoccupied_bg] + end + + if(@merged[@current.name].include?(v)) + fg = @colors[:urgent_fg] + end + + @win.draw_rect(wx, wy, len, @font_height, bg, true) + @win.draw_text(wx + 3, wy + @font_y + 3, v.name, fg) + + wx += len + end + end # }}} + + ## show {{{ + # Show launcher + ## + + def show + @win.show + end # }}} + + ## hide # {{{ + # Hide launcher + ## + + def hide + @win.hide + end # }}} + + end # }}} + end # }}} +end # }}} + +# Implicitly run +if(__FILE__ == $0) + # Set font + #Subtle::Contrib::Merger.font = + # "xft:DejaVu Sans Mono:pixelsize=80:antialias=true" + + Subtle::Contrib::Merger.run +end + +# vim:ts=2:bs=2:sw=2:et:fdm=marker diff --git a/bin/subtle-contrib/ruby/positioner.rb b/bin/subtle-contrib/ruby/positioner.rb new file mode 100644 index 0000000..153b759 --- /dev/null +++ b/bin/subtle-contrib/ruby/positioner.rb @@ -0,0 +1,300 @@ +#!/usr/bin/ruby +# +# @file Positioner +# +# @copyright (c) 2011-2012, Christoph Kappel <unexist@dorfelite.net> +# @version $Id$ +# +# This program can be distributed under the terms of the GNU GPLv2. +# See the file COPYING for details. +# +# Select and tag/untag visible views of current client window +# +# Colors: +# +# Focus - Currently selected view +# View - Other views +# Occupied - Views client is visible +# Urgent - Selected views +# +# Keys: +# +# Left, Up - Move to left +# Right, Down - Move to right +# Escape - Hide/exit +# Space - Select view +# Return - Tag/untag selected views and exit hide/exit +# +# http://subforge.org/projects/subtle-contrib/wiki/Positioner +# + +require 'singleton' + +begin + require 'subtle/subtlext' +rescue LoadError + puts ">>> ERROR: Couldn't find subtlext" + exit +end + +# Check for subtlext version +major, minor, teeny = Subtlext::VERSION.split('.').map(&:to_i) +if major == 0 and minor == 10 and 3216 > teeny + puts ">>> ERROR: positioner needs at least subtle `0.10.3216' (found: %s)" % [ + Subtlext::VERSION + ] + exit +end + +# Positioner class +module Subtle # {{{ + module Contrib # {{{ + class Positioner # {{{ + include Singleton + + # Default values + @@font = '-*-*-medium-*-*-*-14-*-*-*-*-*-*-*' + + # Singleton methods + + ## fonts {{{ + # Set font strings + # @param [String] fonts Fonts array + ## + + def self.font=(font) + @@font = font + end # }}} + + ## run {{{ + # Run expose + ## + + def self.run + self.instance.run + end # }}} + + # Instance methods + + ## initialize {{{ + # Create expose instance + ## + + def initialize + # Values + @colors = Subtlext::Subtle.colors + + # Create main window + @win = Subtlext::Window.new(:x => 0, :y => 0, :width => 1, :height => 1) do |w| + w.name = 'Positioner' + w.font = @@font + w.foreground = @colors[:title_fg] + w.background = @colors[:title_bg] + w.border_size = 0 + end + + # Font metrics + @font_height = @win.font_height + 6 + @font_y = @win.font_y + + # Handler + @win.on(:key_down, method(:key_down)) + @win.on(:draw, method(:redraw)) + end # }}} + + ## run {{{ + # Show and run positioner + ## + + def run + update + show + hide + end # }}} + + private + + ## key_down {{{ + # Key down handler + # @param [String] key Pressed key + ## + + def key_down(key, mods) + ret = true + + case key + when :left, :up # {{{ + idx = @views.index(@cur_sel) + idx -= 1 if 0 < idx + @cur_sel = @views[idx] # }}} + when :right, :down # {{{ + idx = @views.index(@cur_sel) + idx += 1 if idx < (@views.size - 1) + @cur_sel = @views[idx] # }}} + when :space # {{{ + if @selected.include?(@cur_sel) + @selected.delete(@cur_sel) + @unselected << @cur_sel + else + @selected << @cur_sel + end # }}} + when :return # {{{ + tags = @cur_client.tags + + # Add view tags + @selected.each do |sel| + unless @cur_views.include?(sel) + # Find or create tag + tag = Subtlext::Tag.first(sel.name) || Subtlext::Tag.new(sel.name) + tag.save + + # Add tag to view + sel.tag(tag) unless sel.tags.include?(sel.name) + + tags << tag + end + end + + # Remove unselected views from tags + tags -= @unselected.map(&:name).map { |n| Subtlext::Tag.first(n) } + + # Finally apply tags + @cur_client.tags = tags + + ret = false # }}} + when :escape # {{{ + ret = false # }}} + end + + redraw(@win) if ret + + ret + end # }}} + + ## update # {{{ + # Update clients and windows + ## + + def update + @views = Subtlext::View.all + @selected = [] + @unselected = [] + @cur_client = Subtlext::Client.current + @cur_views = @cur_client.views + @cur_sel = Subtlext::View.current + + names = @views.map(&:name) + + # Updated selected list + @cur_client.tags.each do |t| + if names.include?(t.name) + @selected << @views[names.index(t.name)] + end + end + + arrange + end # }}} + + ## arrange {{{ + # Arrange window and subwindows + ## + + def arrange + geo = @cur_client.geometry + width = geo.width #< Max width + height = @font_height + wx = 0 + wy = 0 + len = 0 + wwidth = 0 + + # Arrange client windows + @views.each_with_index do |v, i| + len = @win.font_width(v.name) + 6 + + # Wrap lines + if wx + len > width + wwidth = wx if wx > wwidth + wx = 0 + wy += @font_height + end + + wx += len + end + + # Update geometry + width = 0 == wwidth ? wx : wwidth + height += wy + x = geo.x + ((geo.width - width) / 2) + y = geo.y + ((geo.height - height) / 2) + + @win.geometry = [ x , y, width, height ] + end # }}} + + ## redraw {{{ + # Redraw window content + # @param [Window] w Window instance + ## + + def redraw(w) + @win.clear + + wx = 0 + wy = 0 + len = 0 + + @views.each_with_index do |v, i| + len = @win.font_width(v.name) + 6 + + # Select color + if @views[i] == @cur_sel + fg = @colors[:focus_fg] + bg = @colors[:focus_bg] + elsif @cur_views.include?(@views[i]) + fg = @colors[:occupied_fg] + bg = @colors[:occupied_bg] + else + fg = @colors[:views_fg] + bg = @colors[:views_bg] + end + + if @selected.include?(@views[i]) + fg = @colors[:urgent_fg] + end + + @win.draw_rect(wx, wy, len, @font_height, bg, true) + @win.draw_text(wx + 3, wy + @font_y + 3, v.name, fg) + + wx += len + end + end # }}} + + ## show {{{ + # Show launcher + ## + + def show + @win.show + end # }}} + + ## hide # {{{ + # Hide launcher + ## + + def hide + @win.hide + end # }}} + end # }}} + end # }}} +end # }}} + +# Implicitly run +if __FILE__ == $0 + # Set font + #Subtle::Contrib::Merger.font = + # 'xft:DejaVu Sans Mono:pixelsize=80:antialias=true' + + Subtle::Contrib::Positioner.run +end + +# vim:ts=2:bs=2:sw=2:et:fdm=marker diff --git a/bin/subtle-contrib/ruby/selector.rb b/bin/subtle-contrib/ruby/selector.rb new file mode 100644 index 0000000..13857df --- /dev/null +++ b/bin/subtle-contrib/ruby/selector.rb @@ -0,0 +1,344 @@ +#!/usr/bin/ruby +# +# @file Selector +# +# @copyright (c) 2011-2012, Christoph Kappel <unexist@dorfelite.net> +# @version $Id$ +# +# Client selector that works like the subscription selector in google reader. +# +# Colors: +# +# Focus - Currently selected client +# Occupied - Visible clients on current views +# Unoccupied - Currently no visible clients +# +# Keys: +# +# Left, Up - Move to left +# Right, Down - Move to right +# Tab - Cycle through windows/matches +# Escape - Leave input mode/exit selector +# Return - Focus currently selected and hide/exit selector +# Any capital/digit - Select client prefixed with capital letter/digit +# Any text - Select client with matching instance name +# +# http://subforge.org/projects/subtle-contrib/wiki/Selector +# + +require 'singleton' + +begin + require 'subtle/subtlext' +rescue LoadError + puts ">>> ERROR: Couldn't find subtlext" + exit +end + +# Check for subtlext version +major, minor, teeny = Subtlext::VERSION.split('.').map(&:to_i) +if major == 0 and minor == 10 and 3216 > teeny + puts ">>> ERROR: selector needs at least subtle `0.10.3216' (found: %s)" % [ + Subtlext::VERSION + ] + exit +end + +# Launcher class +module Subtle # {{{ + module Contrib # {{{ + class Selector # {{{ + include Singleton + + # Prefix letters + LETTERS = ((49..57).to_a|(65..89).to_a).map(&:chr) + + # Default values + @@font = '-*-*-medium-*-*-*-14-*-*-*-*-*-*-*' + + # Singleton methods + + ## fonts {{{ + # Set font strings + # @param [String] fonts Fonts array + ## + + def self.font=(font) + @@font = font + end # }}} + + ## run {{{ + # Run expose + ## + + def self.run + self.instance.run + end # }}} + + # Instance methods + + ## initialize {{{ + # Create expose instance + ## + + def initialize + # Values + @colors = Subtlext::Subtle.colors + @expanded = false + @buffer = '' + @x = 0 + @y = 0 + @width = 0 + @height = 0 + + # Create main window + @win = Subtlext::Window.new(:x => 0, :y => 0, :width => 1, :height => 1) do |w| + w.name = 'Selector' + w.font = @@font + w.foreground = @colors[:title_fg] + w.background = @colors[:title_bg] + w.border_size = 0 + end + + # Font metrics + @font_height = @win.font_height + 6 + @font_y = @win.font_y + + # Handler + @win.on :key_down, method(:key_down) + @win.on :draw, method(:redraw) + end # }}} + + ## run {{{ + # Show and run launcher + ## + + def run + update + show + hide + end # }}} + + private + + ## key_down {{{ + # Key down handler + # @param [String] key Pressed key + ## + + def key_down(key, mods) + ret = true + + case key + when :left, :up # {{{ + idx = @clients.index(@current) || 0 + idx -= 1 if 0 < idx + @current = @clients[idx] # }}} + when :right, :down # {{{ + idx = @clients.index(@current) || 0 + idx += 1 if idx < (@clients.size - 1) + @current = @clients[idx] # }}} + when :return # {{{ + @current.focus + + ret = false # }}} + when :escape # {{{ + if @expanded + @buffer = '' + @expanded = false + else + ret = false + end + + arrange # }}} + when :backspace # {{{ + if @expanded + @expanded = (0 < @buffer.chop!.size) + + arrange + end # }}} + when :tab # {{{ + if @buffer.empty? + clients = @clients + else + # Select matching clients + clients = @clients.select do |c| + c.instance.downcase.start_with?(@buffer) + end + end + + unless (idx = clients.index(@current)).nil? + # Cycle between clients + if idx < (clients.size - 1) + idx += 1 + else + idx = 0 + end + + @current = clients[idx] + end # }}} + else # {{{ + str = key.to_s + + if !(idx = LETTERS.index(str)).nil? and idx < @clients.size + @clients[idx].focus + + ret = false + elsif !str.empty? + @buffer << str.downcase + + @clients.each do |c| + if c.instance.downcase.start_with?(@buffer) + @current = c + + break + end + end + + arrange + end # }}} + end + + redraw(@win) if ret + + ret + end # }}} + + ## update # {{{ + # Update clients and windows + ## + + def update + @buffer = '' + @clients = Subtlext::Client.all + @visible = Subtlext::Client.visible + @current = Subtlext::Client.current rescue nil + + arrange + end # }}} + + ## arrange {{{ + # Arrange window + ## + + def arrange + geo = Subtlext::Screen.current.geometry + @width = geo.width * 50 / 100 #< Max width + @height = @font_height + wx = 0 + wy = 0 + len = 0 + wwidth = 0 + + # Toggle expand + if @buffer.empty? + @height = @font_height + @expanded = false + else + @height += @font_height + @expanded = true + end + + # Calculate window width + @clients.each_with_index do |c, i| + str = '%s:%s' % [ LETTERS[i], c.instance ] + len = @win.font_width(str) + 6 + + # Wrap lines + if wx + len > @width + wwidth = wx if wx > wwidth + wx = 0 + wy += @font_height + end + + wx += len + end + + # Update window geometry + @width = 0 == wwidth ? wx : wwidth + @height += wy + @x = geo.x + ((geo.width - @width) / 2) + @y = geo.y + ((geo.height - @height) / 2) + + @win.geometry = [ @x , @y, @width, @height ] + end # }}} + + ## redraw {{{ + # Redraw window content + # @param [Window] w Window instance + ## + + def redraw(w) + wx = 0 + wy = 0 + len = 0 + wwidth = 0 + + @win.clear + + # Render window + @clients.each_with_index do |c, i| + str = '%s:%s' % [ LETTERS[i], c.instance ] + len = @win.font_width(str) + 6 + + # Wrap lines + if wx + len > @width + wwidth = wx if wx > wwidth + wx = 0 + wy += @font_height + end + + # Select color + if @clients[i] == @current + fg = @colors[:focus_fg] + bg = @colors[:focus_bg] + elsif @visible.include?(@clients[i]) + fg = @colors[:occupied_fg] + bg = @colors[:occupied_bg] + else + fg = @colors[:views_fg] + bg = @colors[:views_bg] + end + + @win.draw_rect(wx, wy, len, @font_height, bg, true) + @win.draw_text(wx + 3, wy + @font_y + 3, str, fg) + + wx += len + end + + # Draw input buffer + unless @buffer.empty? + @win.draw_text(6, @height - @font_height + @font_y + 3, + 'Input: %s' % [ @buffer ]) + end + end # }}} + + ## show {{{ + # Show launcher + ## + + def show + @win.show + end # }}} + + ## hide # {{{ + # Hide launcher + ## + + def hide + @win.hide + end # }}} + end # }}} + end # }}} +end # }}} + +# Implicitly run +if __FILE__ == $0 + # Set font + #Subtle::Contrib::Selector.font = + # 'xft:DejaVu Sans Mono:pixelsize=80:antialias=true' + + Subtle::Contrib::Selector.run +end + +# vim:ts=2:bs=2:sw=2:et:fdm=marker diff --git a/bin/subtle-contrib/ruby/styler.rb b/bin/subtle-contrib/ruby/styler.rb new file mode 100644 index 0000000..a3d6e58 --- /dev/null +++ b/bin/subtle-contrib/ruby/styler.rb @@ -0,0 +1,859 @@ +#!/usr/bin/ruby +# +# @file Styler +# +# @copyright (c) 2010-2011, Christoph Kappel <unexist@dorfelite.net> +# @version $Id$ +# +# This program can be distributed under the terms of the GNU GPLv2. +# See the file COPYING for details. +# +# Styler is a helper to create or change subtle color themes +# +# http://subforge.org/projects/subtle-contrib/wiki/Styler +# + +require 'singleton' + +begin + require 'subtle/subtlext' +rescue LoadError + puts ">>> ERROR: Couldn't find subtlext" + exit +end + +begin + require 'gtk2' +rescue LoadError + puts <<EOF +>>> ERROR: Couldn't find the gem `gtk2' +>>> Please install it with following command: +>>> gem install gtk2 +EOF + exit +end + +# Check whether subtle is running +unless(Subtlext::Subtle.running?) + puts ">>> WARNING: Couldn't find running subtle" + #exit +end + +# Check for subtlext version +major, minor, teeny = Subtlext::VERSION.split(".").map(&:to_i) +if(major == 0 and minor == 10 and 2945 > teeny) + puts ">>> ERROR: styler needs at least subtle `0.10.2945' (found: %s)" % [ + Subtlext::VERSION + ] + exit +end + +# Styler class +module Subtle # {{{ + module Contrib # {{{ + class ColorButton + DEFAULT_COLOR = '#000000' + + attr_reader :value + + ## initialize {{{ + # Create a new color button + # @param [String] color Default color + ## + + def initialize(color = DEFAULT_COLOR, &block) + @value = color + @original = color + @callback = nil + + # Create color button + @button = Gtk::ColorButton.new( + Gdk::Color.parse(color) + ) + + # Signal handler + @button.signal_connect('color-set') do |button| + # Assemble hex color + @value = '#%02x%02x%02x' % [ + factor_round((button.color.red.to_f / 65535), 255), + factor_round((button.color.green.to_f / 65535), 255), + factor_round((button.color.blue.to_f / 65535), 255) + ] + + @callback.call(@value) unless(@callback.nil?) + + Styler.instance.render + end + end # }}} + + ## value= {{{ + # Set value + # @param [Fixnum] value New value + ## + + def value=(value) + @value = value + @button.set_color(Gdk::Color.parse(@value)) + end # }}} + + ## attach {{{ + # Attach color button to table + # @param [Table] table A #Gtk::Table + # @param [Fixnum] x X slot + # @param [Fixnum] y Y slot + ## + + def attach(table, x, y) + table.attach(@button, x, x + 1, y, y + 1, Gtk::FILL, Gtk::SHRINK) + end # }}} + + ## reset {{{ + # Reset color button to default color + ## + + def reset + @value = @original + @button.set_color(Gdk::Color.parse(@original)) + end # }}} + + ## callback {{{ + # Add event callback + ## + + def callback(&block) + @callback = block + end # }}} + + private + + def factor_round(val, factor) # {{{ + val = (val * factor + 0.5).floor + val = val > 0 ? val : 0 #< Min + val = val > factor ? factor : val #< Max + + val + end # }}} + end + + class SpinButton + attr_reader :value + + ## initialize {{{ + # Create a new spin button + # @param [Fixnum] value Default value + # @param [Fixnum] min Minimum value + # @param [Fixnum] max Maximum value + ## + + def initialize(value = 0, min = 0, max = 100) + @value = value + @original = value + @callback = nil + + # Create adjustment + adjustment = Gtk::Adjustment.new(value, min, max, 1, 5, 0) + + # Create spin button + @spinner = Gtk::SpinButton.new(adjustment, 0, 0) + @spinner.set_size_request(40, -1) + + # Signal handler + @spinner.signal_connect('value_changed') do |spinner| + @value = spinner.value_as_int + + @callback.call(@value) unless(@callback.nil?) + + Styler.instance.render + end + end # }}} + + ## value= {{{ + # Set value + # @param [Fixnum] value New value + ## + + def value=(value) + @value = value + @spinner.value = value + end # }}} + + ## attach {{{ + # Attach spin button to table + # @param [Table] table A #Gtk::Table + # @param [Fixnum] x X slot + # @param [Fixnum] y Y slot + ## + + def attach(table, x, y) + table.attach(@spinner, x, x + 1, y, y + 1, Gtk::FILL, Gtk::SHRINK) + end # }}} + + ## reset {{{ + # Reset spin button to default value + ## + + def reset + @value = @original + @spinner.value = @original + end # }}} + + ## callback {{{ + # Add event callback + ## + + def callback(&block) + @callback = block + end # }}} + end + + class Style + attr_accessor :buttons + + ## reset {{{ + # Reset elements of style + ## + + def reset + @buttons.each do |k, v| + if v.respond_to?(:reset) + v.reset + elsif(v.is_a?(Hash)) + v.each do |side, spin| + spin.reset if(spin.respond_to?(:reset)) + end + end + end + end # }}} + + ## append {{{ + # Append to notebook + # @param [Notebook] notebook A #Gtk::Notebook + ## + + def append(notebook) + label = Gtk::Label.new(@name) + notebook.append_page(@table, label) + end # }}} + + ## [] {{{ + # Acces values + # @param [Symbol] name Value name + ## + + def [](name) + case @buttons[name] + when ColorButton then @buttons[name].value + when Hash then @buttons[name] + end + end # }}} + + ## attach_label {{{ + # Attach a label to the table + # @param [Fixnum] x X slot + # @param [Fixnum] y Y slot + # @param [String] caption Label caption + ## + + def attach_label(x, y, caption) + label = Gtk::Label.new(caption) + @table.attach(label, x, x + 1, y, y + 1) + end # }}} + + ## sum {{{ + # Get total width of given styles + # @param [Array] List of styles + # @return [Fixnum] Width in pixel + ## + + def sum(list) + width = 0 + + [ :border, :padding, :margin ].each do |button| + list.each do |l| + if(@buttons[button][l].respond_to?(:value)) + width += @buttons[button][l].value + end + end + end + + width + end # }}} + + private + + def compact(name) # {{{ + ret = [] + val = @buttons[name] + + # Compact padding/margin values + if(val[:top].value == val[:bottom].value and + val[:top].value == val[:right].value and + val[:top].value == val[:left].value) + ret << val[:top].value + elsif(val[:top].value == val[:bottom].value and + val[:left].value == val[:right].value) + ret << val[:top].value + ret << val[:left].value + elsif(val[:left].value == val[:right].value) + ret << val[:top].value + ret << val[:left].value + ret << val[:bottom].value + else + ret = val.values.map(&:value) + end + + ret.join(', ') + end # }}} + end + + class StyleNormal < Style + + ## initialize {{{ + # Create a new style element + # @param [String] name Element name + # @param [Array] colors Color array + ## + + def initialize(name, colors) + @name = name + @buttons = {} + + # Table + @table = Gtk::Table.new(5, 7) + + # Labels: Vertical + attach_label(0, 1, 'Foreground') + attach_label(0, 2, 'Background') + attach_label(0, 3, 'Top') + attach_label(0, 4, 'Right') + attach_label(0, 5, 'Bottom') + attach_label(0, 6, 'Left') + + # Labels: Horizontal + attach_label(1, 0, 'Color') + attach_label(2, 0, 'Border') + attach_label(3, 0, 'Padding') + attach_label(4, 0, 'Margin') + + # Color buttons + y = 1 + + { + :fg => 'fg', :bg => 'bg', :top => 'bo_top', + :right => 'bo_right', :bottom => 'bo_bottom', :left => 'bo_left' + }.each do |name, suffix| + @buttons[name] = ColorButton.new( + colors[('%s_%s' % [ @name.downcase, suffix ]).to_sym] || + ColorButton::DEFAULT_COLOR + ) + @buttons[name].attach(@table, 1, y) + + y += 1 + end + + # Spin buttons + x = 2 + + [ :border, :padding, :margin ].each do |name| + @buttons[name] = {} + y = 3 + + [ :top, :right, :bottom, :left ].each do |side| + @buttons[name][side] = SpinButton.new(0) + @buttons[name][side].attach(@table, x, y) + + y += 1 + end + + x += 1 + end + end # }}} + + ## dump {{{ + # Print style element + ## + + def dump + puts "style :#{@name.downcase} do" + puts " foreground '#{@buttons[:fg].value}'" + puts " background '#{@buttons[:bg].value}'" + + # Compact borders + if(@buttons[:top].value == @buttons[:right].value and + @buttons[:top].value == @buttons[:bottom].value and + @buttons[:top].value == @buttons[:left].value and + @buttons[:border][:top].value == @buttons[:border][:left].value and + @buttons[:border][:top].value == @buttons[:border][:bottom].value and + @buttons[:border][:top].value == @buttons[:border][:right].value) + puts " border '#{@buttons[:top].value}', #{@buttons[:border][:top].value}" + else + puts " border_top '#{@buttons[:top].value}', #{@buttons[:border][:top].value}" + puts " border_right '#{@buttons[:right].value}', #{@buttons[:border][:right].value}" + puts " border_bottom '#{@buttons[:bottom].value}', #{@buttons[:border][:bottom].value}" + puts " border_left '#{@buttons[:left].value}', #{@buttons[:border][:left].value}" + end + + puts " padding #{compact(:padding)}" + puts " margin #{compact(:margin)}" + puts "end" + puts + end # }}} + end + + class StyleClients < Style + + ## initialize {{{ + # Create a new other element + # @param [String] name Element name + # @param [Array] colors Color array + ## + + def initialize(name, colors) + @name = name + @table = Gtk::Table.new(5, 7) + @buttons = {} + + # Labels: Vertical + attach_label(0, 1, 'Client active') + attach_label(0, 2, 'Client inactive') + attach_label(0, 3, 'Top') + attach_label(0, 4, 'Right') + attach_label(0, 5, 'Bottom') + attach_label(0, 6, 'Left') + + # Labels: Horizontal + attach_label(1, 0, 'Color') + attach_label(2, 0, 'Border') + attach_label(3, 0, 'Padding') + attach_label(4, 0, 'Margin') + + # Buttons + y = 1 + + [ :active, :inactive ].each do |name| + # Color button + @buttons[name] = ColorButton.new( + colors[('client_%s' % name).to_sym] + ) + @buttons[name].attach(@table, 1, y) + + # Border spinner + sym = ('%s_border' % [ name ]).to_sym + @buttons[sym] = SpinButton.new(2) + @buttons[sym].attach(@table, 2, y) + + y += 1 + end + + # Margin + @buttons[:margin] = {} + + [ :top, :right, :bottom, :left ].each do |side| + @buttons[:margin][side] = SpinButton.new(0) + @buttons[:margin][side].attach(@table, 4, y) + + y += 1 + end + + # Fill remaining fields + @buttons[:fg] = @buttons[:active] + @buttons[:bg] = @buttons[:inactive] + @buttons[:border ] = { + top: @buttons[:active_border], + right: @buttons[:active_border], + bottom: @buttons[:active_border], + left: @buttons[:active_border] + } + + # Just for text placement + @buttons[:padding] = { top: 5, right: 0, bottom: 0, left: 5 } + end # }}} + + ## dump {{{ + # Print style element + ## + + def dump + puts <<STYLE +style :#{@name.downcase} do + active '#{@buttons[:active].value}' + inactive '#{@buttons[:inactive].value}' + margin #{compact(:margin)} +end\n +STYLE + end # }}} + end + + class StyleSubtle < Style + + ## initialize {{{ + # Create a new other element + # @param [String] name Element name + # @param [Array] colors Color array + ## + + def initialize(name, colors) + @name = name + @table = Gtk::Table.new(5, 7) + @buttons = {} + + # Labels: Vertical + attach_label(0, 1, 'Panel top') + attach_label(0, 2, 'Panel bottom') + attach_label(0, 3, 'Background') + attach_label(0, 4, 'Stipple') + attach_label(0, 5, 'Top') + attach_label(0, 6, 'Right') + attach_label(0, 7, 'Bottom') + attach_label(0, 8, 'Left') + + # Labels: Horizontal + attach_label(1, 0, 'Color') + attach_label(2, 0, 'Border') + attach_label(3, 0, 'Padding') + attach_label(4, 0, 'Margin') + + # Buttons + y = 1 + + [ :panel_top, :panel_bottom, :background, :stipple ].each do |name| + # Color button + @buttons[name] = ColorButton.new(colors[name]) + @buttons[name].attach(@table, 1, y) + + y += 1 + end + + # Padding + @buttons[:padding] = {} + + [ :top, :right, :bottom, :left ].each do |side| + @buttons[:padding][side] = SpinButton.new(0) + @buttons[:padding][side].attach(@table, 3, y) + + y += 1 + end + end # }}} + + ## dump {{{ + # Print style element + ## + + def dump + puts "style :#{@name.downcase} do" + + # Compact panel colors + if(@buttons[:panel_top].value == @buttons[:panel_bottom].value) + puts " panel '#{@buttons[:panel_top].value}'" + else + puts " panel_top '#{@buttons[:panel_top].value}'" + puts " panel_bottom '#{@buttons[:panel_bottom].value}'" + end + + puts " background '#{@buttons[:background].value}'" + puts " stipple '#{@buttons[:stipple].value}'" + puts " padding #{compact(:padding)}" + puts "end" + puts + end # }}} + end + + class Styler < Gtk::Window + include Singleton + + BORDER_WIDTH = 2 + WINDOW_WIDTH = 790 + AREA_WIDTH = WINDOW_WIDTH - 16 #< Padding + spacing + CLIENT_WIDTH = (AREA_WIDTH - 2 * BORDER_WIDTH) / 2 + + WINDOW_HEIGHT = 540 + AREA_HEIGHT = 150 + + ## initialize {{{ + # Init window + ## + + def initialize + super + + # Options + set_title('Styler for subtle #{Subtlext::VERSION}') + set_wmclass('styler', 'subtle') + set_resizable(false) + set_keep_above(true) + set_size_request(WINDOW_WIDTH, WINDOW_HEIGHT) + set_window_position(Gtk::Window::POS_CENTER) + stick + + # Signals {{{ + signal_connect('delete_event') do + false + end + + signal_connect('destroy') do + Gtk.main_quit + end # }}} + + # Alignment + align = Gtk::Alignment.new(0.5, 0.5, 0.5, 0.5) + align.set_padding(7, 7, 7, 7) + add(align) + + # Vbox + vbox = Gtk::VBox.new() + align.add(vbox) + + # Frame {{{ + frame = Gtk::Frame.new + frame.set_border_width(0) + frame.set_shadow_type(Gtk::SHADOW_NONE) + vbox.pack_start(frame) # }}} + + # Area {{{ + @area = Gtk::DrawingArea.new + @area.set_size_request(AREA_WIDTH, AREA_HEIGHT) + @area.signal_connect('expose_event') do + expose(@area.window.create_cairo_context) + end + frame.add(@area) # }}} + + # Notebook {{{ + notebook = Gtk::Notebook.new + notebook.set_tab_pos(Gtk::PositionType::LEFT) + vbox.pack_start(notebook, true, false, 5) # }}} + + # Styles {{{ + colors = {} + Subtlext::Subtle.colors.map { |k, v| colors[k] = v.to_hex } + @styles = {} + + # Styles and pages + [ + 'all', 'title', 'views', 'focus', 'urgent', + 'occupied', 'sublets', 'separator' + ].each do |caption| + sym = caption.to_sym + @styles[sym] = StyleNormal.new(caption.capitalize, colors) + @styles[sym].append(notebook) + end + + # Clients + @styles[:clients] = StyleClients.new('Clients', colors) + @styles[:clients].append(notebook) + + # Subtle + @styles[:subtle] = StyleSubtle.new('Subtle', colors) + @styles[:subtle].append(notebook) + + # All: Update border, padding and margin + [ :border, :padding, :margin ].each do |name| + [ :top, :right, :bottom, :left ].each do |side| + @styles[:all].buttons[name][side].callback do |value| + [ + :title, :views, :focus, :urgent, + :occupied, :sublets, :separator + ].each do |style| + @styles[style].buttons[name][side].value = value + end + end + end + end + + # All: Update colors + [ :fg, :bg, :top, :right, :bottom, :left ].each do |name| + @styles[:all].buttons[name].callback do |value| + [ + :title, :views, :focus, :urgent, + :occupied, :sublets, :separator + ].each do |style| + @styles[style].buttons[name].value = value + end + end + end # }}} + + # Hbox + hbox = Gtk::HButtonBox.new + vbox.pack_start(hbox, false, false, 5) + + # Print button {{{ + button = Gtk::Button.new('Print') + hbox.pack_start(button, false, false, 2) + button.signal_connect('clicked') do + @styles.select { |k, v| :all != k }.each do |k, v| + v.dump + end + end # }}} + + # Reset button {{{ + button = Gtk::Button.new('Reset') + hbox.pack_start(button, false, false, 2) + button.signal_connect('clicked') do + @styles.each do |k, v| + if v.respond_to? :reset + v.reset + end + end + + @area.signal_emit('expose-event', nil) + end # }}} + + # Exit button {{{ + button = Gtk::Button.new('Exit') + hbox.pack_start(button, false, false, 2) + button.signal_connect('clicked') do + Gtk.main_quit + end # }}} + + show_all + end # }}} + + ## render {{{ + # Render preview + ## + + def render + @area.signal_emit('expose-event', nil) + end # }}} + + private + + def expose(cr) # {{{ + # Font face, size and height + cr.select_font_face('Arial', 'normal') + cr.set_font_size(12) + + extents = cr.font_extents + @font_height = extents.ascent + extents.descent + 2; + @font_y = (extents.height - 2 + extents.ascent) / 2 + + # Calculate item heights + panel_height = @font_height + + [ + :title, :views, :focus, :urgent, + :occupied, :sublets, :separator + ].each do |panel| + value = @font_height + @styles[panel].sum([ :top, :bottom ]) + + panel_height = value if(value > panel_height) + end + + client_height = AREA_HEIGHT - 2 * panel_height - 2 * BORDER_WIDTH + + # Border + cr.set_source_rgb(1.0, 0.0, 0.0) + cr.rectangle(0, 0, AREA_WIDTH, AREA_HEIGHT) + cr.fill + + # Background + draw_rect(cr, BORDER_WIDTH, BORDER_WIDTH, + AREA_WIDTH - 2 * BORDER_WIDTH, AREA_HEIGHT - 2 * BORDER_WIDTH, + @styles[:subtle][:background]) + + # Panels + draw_rect(cr, BORDER_WIDTH, BORDER_WIDTH, + AREA_WIDTH - 2 * BORDER_WIDTH, panel_height, + @styles[:subtle][:panel_top] + ) + draw_rect(cr, BORDER_WIDTH, BORDER_WIDTH + panel_height + client_height, + AREA_WIDTH - 2 * BORDER_WIDTH, panel_height, + @styles[:subtle][:panel_bottom] + ) + + # Clients + draw_box(cr, BORDER_WIDTH, BORDER_WIDTH + panel_height, CLIENT_WIDTH, + client_height, 'active', @styles[:clients]) + + draw_box(cr, BORDER_WIDTH + CLIENT_WIDTH, BORDER_WIDTH + panel_height, + CLIENT_WIDTH, client_height, 'inactive', @styles[:clients]) + + # Panel items + x = BORDER_WIDTH + + [ + 'focus', 'views', 'urgent', 'occupied', 'title' + ].each do |name| + sym = name.to_sym + extents = cr.text_extents(name) + width = @styles[sym].sum([ :left, :right ]) + extents.x_advance + + draw_box(cr, x, BORDER_WIDTH, width, + panel_height, name, @styles[sym]) + + x += width + end + + # Sublets + x = BORDER_WIDTH + { + 'sublet1' => :sublets, '|' => :separator, 'sublet2' => :sublets + }.each do |name, style| + extents = cr.text_extents(name) + width = @styles[style].sum([ :left, :right ]) + extents.x_advance + + draw_box(cr, x, BORDER_WIDTH + panel_height + client_height, + width, panel_height, name, @styles[style]) + + x += width + end + end # }}} + + def draw_text(cr, x, y, text, color) # {{{ + cr.set_source_color(Gdk::Color.parse(color)) + cr.move_to(x, y + @font_y) + cr.show_text(text) + cr.stroke + end # }}} + + def draw_rect(cr, x, y, width, height, color) # {{{ + cr.set_source_color(Gdk::Color.parse(color)) + cr.rectangle(x, y, width, height) + cr.fill + end # }}} + + def draw_box(cr, x, y, width, height, text, style) # {{{ + mw = style[:margin][:left].value + style[:margin][:right].value + mh = style[:margin][:top].value + style[:margin][:bottom].value + + # Filling + draw_rect(cr, x + style[:margin][:left].value, + y + style[:margin][:top].value, width - mw, height - mh, style[:bg]) + + # Borders + draw_rect(cr, x + style[:margin][:left].value, + y + style[:margin][:top].value, width - mw, + style[:border][:top].value, + text.end_with?('active') ? style[text.to_sym] : style[:top]) + + draw_rect(cr, x + width - style[:border][:right].value - + style[:margin][:right].value, y + style[:margin][:top].value, + style[:border][:right].value, height - mh, + text.end_with?('active') ? style[text.to_sym] : style[:right]) + + draw_rect(cr, x + style[:margin][:left].value, y + height - + style[:border][:bottom].value - style[:margin][:bottom].value, + width - mw, style[:border][:bottom].value, + text.end_with?('active') ? style[text.to_sym] : style[:bottom]) + + draw_rect(cr, x + style[:margin][:left].value, + y + style[:margin][:top].value, style[:border][:left].value, + height - mh, + text.end_with?('active') ? style[text.to_sym] : style[:left]) + + # Text + draw_text(cr, x + style.sum([ :left ]), + y + style.sum([ :top ]), text, style[:fg] + ) + end # }}} + end + end # }}} +end # }}} + +# Implicitly run< +if __FILE__ == $0 + Gtk.init + Subtle::Contrib::Styler.instance + Gtk.main +end + +# vim:ts=2:bs=2:sw=2:et:fdm=marker diff --git a/bin/subtle-contrib/ruby/termstyler.rb b/bin/subtle-contrib/ruby/termstyler.rb new file mode 100644 index 0000000..3ea497a --- /dev/null +++ b/bin/subtle-contrib/ruby/termstyler.rb @@ -0,0 +1,289 @@ +#!/usr/bin/ruby +# +# @file TermStyler +# +# @copyright (c) 2011, Christoph Kappel <unexist@dorfelite.net> +# @version $Id$ +# +# TermStyler is a helper to create or change terminal color themes. +# +# http://subforge.org/projects/subtle-contrib/wiki/TermStyler +# + +begin + require "gtk2" +rescue LoadError + puts <<EOF +>>> ERROR: Couldn't find the gem `gtk2' +>>> Please install it with following command: +>>> gem install gtk2 +EOF + exit +end + +# TermStyler class +module Subtle # {{{ + module Contrib # {{{ + class TermStyler < Gtk::Window # {{{ + WINDOW_WIDTH = 720 + WINDOW_HEIGHT = 420 + AREA_WIDTH = 404 + AREA_HEIGHT = 365 + REGEXP = Regexp.new("^[^!#]*(color[0-9]+|foreground|background)\s*:\s*(#[0-9a-zA-Z]*)") + + ## initialize {{{ + # Init window + ## + + def initialize + super + + # Options + set_title("Styler for terminals") + set_wmclass("termstyler", "subtle") + set_resizable(false) + set_keep_above(true) + set_size_request(WINDOW_WIDTH, WINDOW_HEIGHT) + set_window_position(Gtk::Window::POS_CENTER) + stick + + # Signals + signal_connect("delete_event") do + false + end + + signal_connect("destroy") do + Gtk.main_quit + end + + # Alignment + align = Gtk::Alignment.new(0.5, 0.5, 0.5, 0.5) + align.set_padding(7, 7, 7, 7) + + add(align) + + # Vbox + vbox = Gtk::VBox.new + align.add(vbox) + + # HBox + hbox = Gtk::HBox.new + vbox.pack_start(hbox) + + # Table + table = Gtk::Table.new(4, 9) + hbox.pack_start(table, true, false, 5) + + # Frame + frame = Gtk::Frame.new + frame.set_border_width(0) + frame.set_shadow_type(Gtk::SHADOW_NONE) + hbox.pack_start(frame) + + # Area + @area = Gtk::DrawingArea.new + @area.set_size_request(AREA_WIDTH, AREA_HEIGHT) + @area.signal_connect("expose_event") do + expose(@area.window.create_cairo_context) + end + frame.add(@area) + + # Get colors + load_colors + + # Color buttons + @buttons = {} + row = 0 + idx1 = 0 + idx2 = 8 + + @buttons["foreground"] = color_button(table, row, 0, "foreground") + @buttons["background"] = color_button(table, row, 2, "background") + + 8.times do |i| + row += 1 + name1 = "color#{idx1 + i}" + name2 = "color#{idx2 + i}" + + @buttons[name1] = color_button(table, row, 0, name1) + @buttons[name2] = color_button(table, row, 2, name2) + end + + # Hbox + hbox = Gtk::HButtonBox.new + vbox.pack_start(hbox, false, false, 5) + + # Print button + button = Gtk::Button.new("Print") + hbox.pack_start(button, false, false, 2) + button.signal_connect("clicked") do + print_pair("", "foreground", "background") + print_pair("black", "color0", "color8") + print_pair("red", "color1", "color9") + print_pair("green", "color2", "color10") + print_pair("yellow", "color3", "color11") + print_pair("blue", "color4", "color12") + print_pair("magenta", "color5", "color13") + print_pair("cyan", "color6", "color14") + print_pair("white", "color7", "color15") + end + + # Reset button + button = Gtk::Button.new("Reset") + hbox.pack_start(button, false, false, 2) + button.signal_connect("clicked") do + load_colors + + # Reset color buttons + @colors.each do |k, v| + @buttons[k].set_color(Gdk::Color.parse(v)) + end + + @area.signal_emit("expose-event", nil) + end + + # Exit button + button = Gtk::Button.new("Exit") + hbox.pack_start(button, false, false, 2) + button.signal_connect("clicked") do + Gtk.main_quit + end + + show_all + end # }}} + + private + + def print_pair(name, col1, col2) # {{{ + puts "!#{name}" unless(name.empty?) + puts "*%-11s %s" % [ col1 + ":", @colors[col1] ] + puts "*%-11s %s" % [ col2 + ":", @colors[col2] ] + end # }}} + + def load_colors # {{{ + @colors = Hash[*(16.times.to_a.map { |i| + [ "color#{i}", "#000000" ] + } << ["forground", "#ffffff", "background", "#000000"]).flatten] + + # Load and parse Xdefaults + File.open("#{ENV["HOME"]}/.Xdefaults") do |f| + while(line = f.gets) + if line.match(REGEXP) + @colors[$~[1]] = $~[2] + end + end + end + rescue + end # }}} + + def scale_round(val, factor) # {{{ + val = (val * factor + 0.5).floor + val = val > 0 ? val : 0 #< Min + val = val > factor ? factor : val #< Max + + val + end # }}} + + def color_button(table, row, col, name) # {{{ + caption = name.to_s.split(/[^a-z0-9]/i).map { |w| + w.capitalize + }.join(" ") + label = Gtk::Label.new(caption) + button = Gtk::ColorButton.new( + Gdk::Color.parse(@colors[name]) + ) + + # Align + align = Gtk::Alignment.new(1.0, 0.0, 0, 0) + align.add(label) + + # Signal handler + button.signal_connect("color-set") do |button| + begin + # Assemble and update hex color + @colors[name] = "#%02X%02X%02X" % [ + scale_round((button.color.red.to_f / 65535), 255), + scale_round((button.color.green.to_f / 65535), 255), + scale_round((button.color.blue.to_f / 65535), 255) + ] + + @area.signal_emit("expose-event", nil) + rescue => error + puts error, error.backtrace + end + end + + # Attach to table + table.attach(align, 0 + col, 1 + col, 0 + row, 1 + row, + Gtk::SHRINK, Gtk::SHRINK, 5, 5) + table.attach(button, 1 + col, 2 + col, 0 + row, 1 + row, + Gtk::SHRINK, Gtk::SHRINK, 5, 5) + + return button + end # }}} + + def expose(cr) # {{{ + # Border + cr.set_source_rgb(1.0, 0.0, 0.0) + cr.rectangle(0, 0, AREA_WIDTH, AREA_HEIGHT) + cr.fill + + # Rects + width = (AREA_WIDTH - 4) / 8 + + 8.times do |i| + draw_rect(cr, 2 + width * i, 2, width, AREA_HEIGHT - 4, @colors["color#{i}"]) + end + + # Fore-/background + draw_rect(cr, 2, 2, AREA_WIDTH - 4, 38, @colors["background"]) + + # Labels + xary = 8.times.map { |i| 10 + width * i } + y = 0 + + 8.times do |i| + draw_text(cr, "Norm", xary, 60 + y, @colors["color#{i}"]) + draw_text(cr, "Bold", xary, 75 + y, @colors["color#{8 + i}"], true) + + y += 40 + end + + draw_text(cr, "Col", xary, 24, @colors["foreground"]) + end # }}} + + def draw_rect(cr, x, y, width, height, color) # {{{ + cr.set_source_color(Gdk::Color.parse(color)) + cr.rectangle(x, y, width, height) + cr.fill + end # }}} + + def draw_text(cr, text, xary, y, color, bold = false) # {{{ + cr.set_source_color(Gdk::Color.parse(color)) + cr.select_font_face("Arial", Cairo::FONT_SLANT_NORMAL, + bold ? Cairo::FONT_WEIGHT_BOLD : Cairo::FONT_WEIGHT_NORMAL + ) + cr.set_font_size(14) + + xary = [ xary ] if(xary.is_a?(Fixnum)) + + # Draw text on each xary pos + xary.each do |x| + cr.move_to(x, y) + cr.show_text(text) + end + + cr.stroke + end # }}} + end # }}} + end # }}} +end # }}} + +# Implicitly run< +if __FILE__ == $0 + Gtk.init + Subtle::Contrib::TermStyler.new + Gtk.main +end + +# vim:ts=2:bs=2:sw=2:et:fdm=marker diff --git a/bin/subtle-contrib/ruby/vitag.rb b/bin/subtle-contrib/ruby/vitag.rb new file mode 100644 index 0000000..4a1e6c8 --- /dev/null +++ b/bin/subtle-contrib/ruby/vitag.rb @@ -0,0 +1,127 @@ +#!/usr/bin/ruby +# +# @file Vitag +# +# @copyright (c) 2010-2011, Christoph Kappel <unexist@dorfelite.net> +# @version $Id$ +# +# This program can be distributed under the terms of the GNU GPLv2. +# See the file COPYING for details. +# +# Vitag is a helper to edit window/view tagging with any $EDITOR +# +# http://subforge.org/projects/subtle-contrib/wiki/Vitag +# + +require 'tempfile' +require 'digest/md5' + +begin + require 'subtle/subtlext' +rescue LoadError + puts ">>> ERROR: Couldn't find subtlext" + exit +end + +# Check if $EDITOR is set +if ENV['EDITOR'].nil? + puts <<-EOF +>>> ERROR: Couldn't find $EDITOR envorinment variable +>>> Please set it like this: export EDITOR=vim + EOF + exit +end + +# Check whether subtle is running +unless(Subtlext::Subtle.running?) + puts ">>> ERROR: Couldn't find running subtle" + exit +end + +# Check for subtlext version +major, minor, teeny = Subtlext::VERSION.split('.').map(&:to_i) +if major == 0 and minor == 10 and 3104 > teeny + puts ">>> ERROR: vitag needs at least subtle `0.10.3104' (found: %s)" % [ + Subtlext::VERSION + ] + exit +end + +# Collect views and clients +views = Subtlext::View.all +clients = Subtlext::Client.all + +# Create temp file +temp = Tempfile.new('vitag-') + +# Fill in tags +temp.puts('# Views') + +views.each do |v| + temp.puts('@%s %s' % [ + v.name, + v.tags.map { |t| '#%s' % [ t ] }.join(' ') + ]) +end + +# Fill in tags +temp.puts('') +temp.puts('# Clients') + +clients.each do |c| + # Remove hashes from string + name = c.to_str.split('#').first + + temp.puts('%s (%s) %s' % [ + name, c.instance, c.tags.map { |t| '#%s' % [ t ] }.join(' ') + ]) +end + +temp.flush + +# Store checksum for check +md5 = Digest::MD5.file(temp.path) + +# Start editor +system('$EDITOR %s' % [ temp.path ]) + +temp.rewind + +# Check for changes +if md5 != Digest::MD5.file(temp.path) + + # Read temp file + temp.readlines.each do |line| + + # Handle lines + case line[0] + when '@' then cur = views.shift + when '#', ' ', "\n" then next + else cur = clients.shift + end + + # Select tags and sanitize + tags = line.split('#')[1..-1].map(&:rstrip) + + # Check for valid object + if cur and tags + + # Find or create tags + tags.map! do |name| + tag = Subtlext::Tag.first(name) || Subtlext::Tag.new(name) + tag.save + + tag + end + + # Finally assign tags + cur.tags = tags + + cur = nil + end + end +end + +temp.close + +# vim:ts=2:bs=2:sw=2:et:fdm=marker |