View Single Post
Old January 8th, 2008   #50
rougier
First Cup of Ubuntu
 
Join Date: Dec 2006
Beans: 2
Re: WriteRoom/Darkroom/?

Hi all,

I've heavily modified the pyroom.py posted by Nowhereman to allows several buffers, undo/redo and line numbers. There are three different styles defined and it is quite easy to add new styles.

Usage:
----------

pyroom.py [-style] file1 file2 ...
style can be either 'blue', 'green' or 'darkgreen'


Commands:
-----------------

Control-H: Show help in a new buffer
Control-I: Show buffer information
Control-L: Toggle line number
Control-N: Create a new buffer
Control-O: Open a file in a new buffer
Control-Q: Quit
Control-S: Save current buffer
Control-Shift-S: Save current buffer as
Control-W: Close buffer and exit if it was the last buffer
Control-Y: Redo last typing
Control-Z: Undo last typing
Control-Left: Switch to previous buffer
Control-Right: Switch to next buffer

Warnings:
--------------
No autosave.
No question whether to close a modified buffer or not


Nicolas


Code:
#! /usr/bin/env python
# ------------------------------------------------------------------------------
# PyRoom - A clone of WriteRoom
# Copyright (c) 2007 Nicolas P. Rougier & NoWhereMan
#
# This program is free software: you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free Software
# Foundation, either version 3 of the License, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along with
# this program.  If not, see <http://www.gnu.org/licenses/>.
# ------------------------------------------------------------------------------
#
# Based on code posted on ubuntu forums by NoWhereMan (www.nowhereland.it)
#  (Ubuntu thread was "WriteRoom/Darkroom/?")
#  
# ------------------------------------------------------------------------------

import traceback
import sys, os.path
import gobject, gtk, pango, gtksourceview

# Some styles
styles = {
    'darkgreen' : {
        'name'       : 'darkgreen',
        'background' : '#000000',
        'foreground' : '#007700',
        'lines'      : '#001100',
        'border'     : '#001100',
        'info'       : '#007700',
        'font'       : 'DejaVu Sans Mono 14',
        'padding'    : 6,
        'size'       : [0.6, 0.95] # [width, height]
        },
    'green': {
        'name'       : 'green',
        'background' : '#000000',
        'foreground' : '#00ff00',
        'lines'      : '#007700',
        'border'     : '#003300',
        'info'       : '#00ff00',
        'font'       : 'DejaVu Sans Mono 14',
        'padding'    : 6,
        'size'       : [0.6, 0.95] # [width, height]
        },
    'blue': {
        'name'       : 'blue',
        'background' : '#0000ff',
        'foreground' : '#ffffff',
        'lines'      : '#5555ff',
        'border'     : '#3333ff',
        'info'       : '#ffffff',
        'font'       : 'DejaVu Sans Mono 14',
        'padding'    : 6,
        'size'       : [0.6, 0.95] # [width, height]
        }
    }

FILE_UNNAMED = "* Unnamed *"

HELP = """PyRoom - an adaptation of write room
Copyright (c) 2007 Nicolas Rougier, NoWhereMan

This program is free software: you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free Software
Foundation, either version 3 of the License, or (at your option) any later
version.

Usage:
------

pyroom.py [-style] file1 file2 ...
style can be either 'blue', 'green', 'darkgreen'


Commands:
---------
Control-H: Show help in a new buffer
Control-I: Show buffer information
Control-L: Toggle line number
Control-N: Create a new buffer
Control-O: Open a file in a new buffer
Control-Q: Quit
Control-S: Save current buffer
Control-Shift-S: Save current buffer as
Control-W: Close buffer and exit if it was the last buffer
Control-Y: Redo last typing
Control-Z: Undo last typing
Control-Left: Switch to previous buffer
Control-Right: Switch to next buffer

Warnings:
---------
No autosave.
No question whether to close a modified buffer or not
"""


class FadeLabel (gtk.Label):
    """ GTK Label with timed fade out effect """

    active_duration = 3000      # Fade start after this time
    fade_duration   = 1500      # Fade duration

    def __init__ (self, str='', active_color=None, inactive_color=None):
        gtk.Label.__init__ (self, str)
        if not active_color:
            active_color = "#ffffff"
        self.active_color = active_color
        if not inactive_color:
            inactive_color = "#000000"
        self.inactive_color = inactive_color
        self.idle = 0

    def set_text (self, str, duration=None):
        if not duration:
            duration = self.active_duration
        self.modify_fg (gtk.STATE_NORMAL,gtk.gdk.color_parse(self.active_color))
        gtk.Label.set_text (self, str)
        if self.idle:
            gobject.source_remove (self.idle)
        self.idle = gobject.timeout_add (duration, self.fade_start)

    def fade_start (self):
        self.fade_level = 1.0
        if self.idle:
            gobject.source_remove (self.idle)
        self.idle = gobject.timeout_add (25, self.fade_out)
        
    def fade_out (self):
        color = gtk.gdk.color_parse (self.inactive_color)
        r1, g1, b1 = color.red, color.green, color.blue
        color = gtk.gdk.color_parse (self.active_color)
        r2, g2, b2 = color.red, color.green, color.blue
        r = r1 + int(self.fade_level*abs (r1-r2))
        g = g1 + int(self.fade_level*abs (g1-g2))
        b = b1 + int(self.fade_level*abs (b1-b2))
        self.modify_fg (gtk.STATE_NORMAL, gtk.gdk.Color (r,g,b))
        self.fade_level -= 1.0 / (self.fade_duration / 25)
        if (self.fade_level > 0):
            return True
        self.idle = 0
        return False

	
class PyRoom:
    """ The PyRoom class"""

    buffers = []
    current = 0

    def __init__(self, style):

        self.style = style

        # Main window
        self.window = gtk.Window (gtk.WINDOW_TOPLEVEL)
        self.window.set_name ("PyRoom")
        self.window.fullscreen ()
        self.window.connect ("delete_event", self.delete_event)
        self.window.connect ("destroy", self.destroy)

        self.textbox = gtksourceview.SourceView()
        self.new_buffer()
        self.textbox.connect ('scroll-event', self.scroll_event)
        self.textbox.connect ('key-press-event', self.key_press_event)
        self.textbox.set_wrap_mode (gtk.WRAP_WORD)

        self.fixed = gtk.Fixed()
        self.vbox = gtk.VBox()
        self.window.add (self.fixed)
        self.fixed.put (self.vbox, 0,0)

        self.boxout = gtk.EventBox()
        self.boxout.set_border_width (1)
        self.boxin = gtk.EventBox()
        self.boxin.set_border_width (1)
        self.vbox.pack_start (self.boxout, True, True, 6)
        self.boxout.add (self.boxin)

        self.scrolled = gtk.ScrolledWindow()
        self.boxin.add (self.scrolled)
        self.scrolled.add (self.textbox)
        self.scrolled.set_policy(gtk.POLICY_NEVER, gtk.POLICY_NEVER)
        self.scrolled.show()
        self.scrolled.set_property ("resize-mode", gtk.RESIZE_PARENT)
        self.textbox.set_property ("resize-mode", gtk.RESIZE_PARENT)
        self.vbox.set_property ("resize-mode", gtk.RESIZE_PARENT)
        self.vbox.show_all()

        # Status
        self.status = FadeLabel ()
        self.hbox = gtk.HBox ()
        self.hbox.set_spacing (12)        
        self.hbox.pack_end (self.status, True, True, 0)
        self.vbox.pack_end (self.hbox, False, False, 0)
        self.status.set_alignment (0.0, 0.5)
        self.status.set_justify (gtk.JUSTIFY_LEFT)
        self.apply_style ()
        self.window.show_all ()        
        self.status.set_text ('Welcome to PyRoom 1.0, type Control-H for help')

    def delete_event(self, widget, event, data=None):
        """ Quit """
        return False

    def destroy(self, widget, data=None):
        """ Quit """
        gtk.main_quit()

    def key_press_event (self, widget, event):
        """ key press event dispatcher """

        bindings = {
            gtk.keysyms.Left : self.prev_buffer,
            gtk.keysyms.Right: self.next_buffer,
            gtk.keysyms.h:     self.show_help,
            gtk.keysyms.i:     self.show_info,
            gtk.keysyms.l:     self.toggle_lines,
            gtk.keysyms.n:     self.new_buffer,
            gtk.keysyms.o:     self.open_file,
            gtk.keysyms.q:     self.quit,
            gtk.keysyms.s:     self.save_file,
            gtk.keysyms.w:     self.close_buffer,
            gtk.keysyms.y:     self.redo,
            gtk.keysyms.z:     self.undo
            }
        if event.state & gtk.gdk.CONTROL_MASK:
            # Special case for Control-Shift-s
            if (event.state & gtk.gdk.SHIFT_MASK):
                print event.keyval
            if (event.state & gtk.gdk.SHIFT_MASK) and (event.keyval == gtk.keysyms.S):
                self.save_file_as()                
                return True
            if bindings.has_key (event.keyval):
                bindings [event.keyval]()
                return True
        return False

    def scroll_event (self, widget, event):
        """" Scroll event dispatcher """

        if event.direction == gtk.gdk.SCROLL_UP:
            self.scroll_up()
        elif event.direction == gtk.gdk.SCROLL_DOWN:
            self.scroll_down()

    def show_help (self):
        """ Create a new buffer and inserts help """
        
        buffer = self.new_buffer()
        buffer.begin_not_undoable_action()
        buffer.set_text (HELP)
        buffer.end_not_undoable_action()

    def new_buffer (self):
        """ Create a new buffer """

        buffer = gtksourceview.SourceBuffer()
        buffer.set_check_brackets (False)
        buffer.set_highlight (False)
        buffer.filename = FILE_UNNAMED
        self.buffers.insert (self.current+1, buffer)
        buffer.place_cursor(buffer.get_start_iter())
        self.next_buffer ()
        return buffer

    def close_buffer (self):
        """ Close current buffer """

        if len(self.buffers) > 1:
            self.buffers.pop (self.current)
            self.current = min (len(self.buffers)-1, self.current)
            self.set_buffer (self.current)
        else:
            quit()

    def set_buffer (self, index):
        """ Set current buffer """

        if index >= 0 and index < len(self.buffers):
            self.current = index
            buffer = self.buffers[index]
            self.textbox.set_buffer (buffer)
            if hasattr (self, 'status'):
                self.status.set_text ('Switching to buffer %d (%s)' % (
                        (self.current+1), buffer.filename))

    def next_buffer (self):
        """ Switch to next buffer """        

        if self.current < (len(self.buffers)-1):
            self.current += 1
        else:
            self.current = 0
        self.set_buffer (self.current)

    def prev_buffer (self):
        """ Switch to prev buffer """

        if self.current > 0:
            self.current -= 1
        else:
            self.current = len(self.buffers)-1
        self.set_buffer (self.current)

    def apply_style (self, style=None):
        """ """

        if style:
            self.style = style
        self.window.modify_bg (gtk.STATE_NORMAL,
                               gtk.gdk.color_parse(self.style['background']))
        self.textbox.modify_bg (gtk.STATE_NORMAL,
                                gtk.gdk.color_parse(self.style['background']))
        self.textbox.modify_base (gtk.STATE_NORMAL,
                                  gtk.gdk.color_parse(self.style['background']))
        self.textbox.modify_text (gtk.STATE_NORMAL,
                                  gtk.gdk.color_parse(self.style['foreground']))
        self.textbox.modify_fg (gtk.STATE_NORMAL,
                                gtk.gdk.color_parse(self.style['lines']))
        self.status.active_color = self.style['foreground']
        self.status.inactive_color = self.style['background']
        self.boxout.modify_bg (gtk.STATE_NORMAL,
                               gtk.gdk.color_parse (self.style['border']))
        self.textbox.modify_font (pango.FontDescription (self.style['font']))

        gtk.rc_parse_string (
            """
	    style "pyroom-colored-cursor" {
                GtkTextView::cursor-color = '""" + self.style['foreground'] + """'
            }
            class "GtkWidget" style "pyroom-colored-cursor"
	    """)
        w,h = gtk.gdk.screen_width(), gtk.gdk.screen_height()
        width  = int (self.style['size'][0]*w)
        height = int (self.style['size'][1]*h)
        self.vbox.set_size_request (width, height)
        self.fixed.move (self.vbox,
                         int((1-self.style['size'][0])*w/2),
                         int((1-self.style['size'][1])*h/2))
        self.textbox.set_border_width (self.style['padding'])
                    
    def word_count(self, buffer):
        """ Word count in a text buffer """

        i1 = buffer.get_start_iter()
        i2 = i1.copy()
        i2.forward_word_end()
        i = 0
        while i2.get_offset() <> i1.get_offset():
            i += 1
            i1 = i2.copy()
            i2.forward_word_end()
        return i

    def quit(self):
        """ quit pyroom """
        gtk.main_quit()

    def show_info (self):
        """ Display buffer information on status label for 5 seconds """

        buffer = self.buffers[self.current]
        if buffer.can_undo() or buffer.can_redo():
            status = ' (modified)'
        else:
            status= ''
        self.status.set_text (
            "Buffer %d: %s%s, %d byte(s), %d word(s), %d line(s)" % (
                self.current+1,
                buffer.filename,
                status,
                buffer.get_char_count(),
                self.word_count (buffer),
                buffer.get_line_count(),
                ), 5000
            )

    def scroll_down (self):
        """ Scroll window down """
        adj = self.scrolled.get_vadjustment()
        if adj.upper > adj.page_size:
            adj.value = min (adj.upper-adj.page_size,
                             adj.value+adj.step_increment)

    def scroll_up (self):
        """ Scroll window up """
        adj = self.scrolled.get_vadjustment()
        if adj.value  > adj.step_increment:
            adj.value -= adj.step_increment
        else:
            adj.value = 0

    def undo (self):
        """ Undo last typing """
        buffer = self.textbox.get_buffer()
        if buffer.can_undo():
            buffer.undo()
        else:
            self.status.set_text ('No more undo !')

    def redo (self):
        """ Redo last typing """
        buffer = self.textbox.get_buffer()
        if buffer.can_redo():
            buffer.redo()
        else:
            self.status.set_text ('No more redo !')

    def toggle_lines (self):
        """ Toggle lines number """
        b = self.textbox.get_show_line_numbers()
        b = not b
        self.textbox.set_show_line_numbers (b)

    def open_file(self):
        """ Open file """
        chooser = gtk.FileChooserDialog(
            'PyRoom', self.window, gtk.FILE_CHOOSER_ACTION_OPEN,
            buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
                     gtk.STOCK_OPEN, gtk.RESPONSE_OK))
        chooser.set_default_response(gtk.RESPONSE_OK)

        res = chooser.run()
        if res == gtk.RESPONSE_OK:
            buffer = self.new_buffer ()
            buffer.filename = chooser.get_filename()
            try:
                f = open (buffer.filename, 'r')
                buffer = self.buffers[self.current]
                buffer.begin_not_undoable_action()
                utf8 = unicode (f.read(), 'utf-8')
                buffer.set_text(utf8)
                buffer.end_not_undoable_action()
                f.close()
                self.status.set_text('File ' + buffer.filename + ' open')
            except:
                buffer.set_text ('Unable to open %s\n %s\n' %
                                 (buffer.filename, traceback.format_exc()))
                self.status.set_text('Failed to open ' + buffer.filename)
                buffer.filename = FILE_UNNAMED
        else:
            self.status.set_text( 'Closed, no files selected' )
        chooser.destroy()
        
    def save_file(self):
        """ Save file """

        buffer = self.buffers[self.current]
        if buffer.filename != FILE_UNNAMED:
            f = open (buffer.filename, 'w')
            txt = buffer.get_text (buffer.get_start_iter(),
                                   buffer.get_end_iter())
            f.write(txt)
            f.close()
            buffer.begin_not_undoable_action()
            buffer.end_not_undoable_action()
            self.status.set_text('File ' + buffer.filename + ' saved')
        else:
            self.save_file_as ()
            
    def save_file_as (self):
        """ Save file as """

        buffer = self.buffers[self.current]
        chooser = gtk.FileChooserDialog(
            'PyRoom', self.window, gtk.FILE_CHOOSER_ACTION_SAVE,
            buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
                     gtk.STOCK_SAVE,   gtk.RESPONSE_OK))
        chooser.set_default_response (gtk.RESPONSE_OK)
        if buffer.filename != FILE_UNNAMED:
            chooser.set_filename (buffer.filename)
        res = chooser.run()
        if res == gtk.RESPONSE_OK:
            buffer.filename = chooser.get_filename()
            self.save_file ()
        else:
            self.status.set_text( 'Closed, no files selected' )
        chooser.destroy()


if __name__ == "__main__":
    style = 'blue'
    files = []

    # Look for style and file on command line
    for arg in sys.argv[1:]:
        if arg[0] == '-':
            t = arg[1:]
            if styles.has_key(arg[1:]):
                style = arg[1:]
        else:
            files.append (arg)

    # Create relevant buffers for file and load them
    pyroom = PyRoom (styles[style])
    if len(files):
        for file in files:
            buffer = pyroom.new_buffer()
            buffer.filename = file
            if os.path.exists (file):
                try:
                    print file
                    f = open (file, 'r')
                    buffer.begin_not_undoable_action()
                    utf8 = unicode (f.read(), 'utf-8')
                    buffer.set_text (utf8)
                    buffer.end_not_undoable_action()
                    f.close()
                except:
                    buffer.set_text ('Unable to open ' + buffer.filename + '\n')
                    buffer.set_text ('Unable to open %s\n %s\n' %
                                     (buffer.filename, traceback.format_exc()))
                    buffer.filename = FILE_UNNAMED
        pyroom.set_buffer (0)
        pyroom.close_buffer()            
    gtk.main()
rougier is offline   Reply With Quote