CUBISTCODE

CUBISTCODE

Edile Python Text Editor: A PyGTK text editor in one source code file

#!/usr/bin/env python

########################################################################
# 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 2 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, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
# -----------------------------------------------------------------------------
#
# This is Edile: A PyGTK+ text editor.
# http://edile.googlecode.com

# Edile is a basic but useful text editor implemented in one source code file.
# It requires only PyGTK but if you have pygtksourceview2 installed it will use that.
#
# This file is based on part of the tutorial: Linux GUI Programming with GTK+ and Glade3
# http://www.micahcarrick.com/12-24-2007/gtk-glade-tutorial-part-1.html
########################################################################

# edit default configuration here
#=======================================================================

CONF_FONT                           =       "inconsolata 10"
CONF_HIGHLIGHT_CURRENT_LINE         =       True
CONF_HIGHLIGHT_MATCHING_BRACKETS    =       True
CONF_OVERWRITE                      =       False              # insert/overwrite mode
CONF_SHOW_LINE_NUMBERS              =       True
CONF_AUTO_INDENT                    =       True
CONF_INDENT_ON_TAB                  =       True
CONF_SHOW_RIGHT_MARGIN              =       True
CONF_RIGHT_MARGIN_POSITION          =       72
CONF_HIGHLIGHT_SYNTAX               =       True
CONF_SPACES_INSTEAD_OF_TABS         =       True
CONF_TAB_WIDTH                      =       4
CONF_INDENT_WIDTH                   =       -1                  # -1 means use same as tab width
CONF_MAX_UNDO_LEVELS                =       50                  # -1 means no limit

CONF_HIGHLIGHT_CHANGES_SINCE_SAVE   =       True

# color for highlighting changes since last save
CONF_CHANGE_HIGHLIGHT               =       "dark slate gray"      # from /etc/X11/rgb.txt

# color for highlighting find/replace matches
CONF_FIND_HIGHLIGHT                 =       "IndianRed4"

# gtksrcview color scheme
CONF_STYLE_SCHEME                   =       ("oblivion","kate","tango","classic")

# 'note pad'. will open this file on startup instead of untitled. empty string means no default file.
CONF_DEFAULT_FILE                   =       ""

# whether or not to load plugins. off by default.
# plugins provide a way for you to customise edile for your purpose or environment
# without changing the application's source code.
CONF_LOAD_PLUGINS                   =       False

# whether to load plugins when running as the super user. 'no' by default.
CONF_LOAD_PLUGINS_AS_ROOT           =       False

# url of file to load plugins from. any scheme should work (e.g. file:///, http://)
# default is http://edile.googlecode.com/files/edile_plugins-0.1.8.py
# see that file for a description of the plugin interface.
# as of version 0.1.8 options can also be set from that file.
CONF_PLUGIN_LOCATION             =       "http://edile.googlecode.com/files/edile_plugins-0.1.7.py"
# sha 256 hash of the plugin file for authentication. empty string means no authentication.
CONF_PLUGIN_SHA256               =       "eca99d7b7ae2042a742366d7b389857d7fa2ee496573d7dee4ebb5a885b53288"

# move whichever you want as default into the first position
CONF_WRAP                           =   ('None','Character','Word')
CONF_SMART_HOME_END_TYPE            =   ('Before','Disabled','After','Always')

#=======================================================================
# end configuration

# stuff here you probably shouldn't edit
#=======================================================================
EDILE_VERSION = '0.2'
EDILE_URL = 'http://edile.googlecode.com'
EDILE_NAME = 'Edile'
EDILE_DESCRIPTION = 'a pygtk text editor in one file.'
#=======================================================================

# here begins the program edile

# TODO: command line options (verbosity, language, conf options)
#       improve find code. handle everything internal to edile w/o relying on gtksrcview features eg. case sensitivity
#       config option for encoding to use when saving file instead of always file's encoding
#		generally firm everything up. condense and refactor code, exception handling, etc.

# UI definition. GTK builder XML string
U_I = '''


  
    
      
        
          
            file_menu
            _File
          
        
        
          
            gtk-new
            new_menu_item
            _New
            
          
        
        
          
            gtk-new
            new_window_menu_item
            N_ew Window
            
          
          
        
        
          
            gtk-open
            open_menu_item
            _Open
            
          
        
         
          
            gtk-open
            open_in_new_window_menu_item
            Open _In New Window
            
          
          
        
       
          
            gtk-save
            save_menu_item
            _Save
            
          
        
        
          
            gtk-save-as
            save_as_menu_item
            Save _As
            
          
        
        
          
            gtk-reload
            reload_menu_item
            Reload
            
          
        
        
          
            gtk-execute
            execute_menu_item
            _Run In Terminal
            
          
          
        
        
          
            gtk-close
            close_menu_item
            _Close
            
          
        
        
          
            gtk-quit
            quit_menu_item
            _Quit
            
          
        
        
          
            edit_menu
            _Edit
          
        
        
          
            gtk-undo
            undo_menu_item
            _Undo
            
          
        
        
        
          
            gtk-redo
            redo_menu_item
            _Redo
            
          
        
        
        
          
            gtk-cut
            cut_menu_item
            Cu_t
            
          
        
        
          
            gtk-copy
            copy_menu_item
            _Copy
            
          
        
        
          
            gtk-paste
            paste_menu_item
            _Paste
            
          
        
        
          
            gtk-select-all
            select_all_menu_item
            Select _All
            
          
        
        
        
          
            gtk-delete
            delete_menu_item
            _Delete
            
          
        
         
          
            search_menu
            _Search
          
        
        
          
            find_prev_selected_menu_item
            Find Previous Selected
            
          
        
        
        
          
            find_next_selected_menu_item
            Find Next Selected
            
          
        
        
        
          
            gtk-find
            show_find_menu_item
            _Find & Replace
            
          
        
        
        
          
            gtk-go-forward
            find_next_menu_item
            Find _Next
            
          
        
        
        
          
            gtk-go-back
            find_previous_menu_item
            Find _Previous
            
          
          
        
        
          
            find_all_menu_item
            Find All
            
          
          
        
        
        
          
            replace_all_menu_item
            Replace All
            
          
        
       
          
            view_menu
            _View
          
        
       
          
            wrap_menu
            W_rap
          
        
        
          
            wrap_none_menu_item
            _None
            
            
          
        
        
          
            wrap_char_menu_item
            _Character
            wrap_none_menu_item
            
          
        
        
          
            wrap_word_menu_item
            _Word
            wrap_none_menu_item
            
          
        
        
          
            select_font_menu_item
            Select Font...
            
          
        
        
          
            help_menu
            _Help
          
        
         
          
            
            view_source_menu_item
            _View Source
            
          
          
        
       
          
            gtk-about
            about_menu_item
            _About
            
          
        
      
    
    
      
        
          
          
          
          
          
          
          
          
          
          
          
          
          
          
        
        
          
          
          
          
          
          
          
          
          
        
        
          
          
          
          
          
          
          
          
          
          
        
        
          
          
          
          
          
          
          
        
        
          
          
        
      
    
  
  
    GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK
    Edile Text Editor
    540
    660
    
    
    
      
        True
        GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK
        
          
            True
            GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK
          
          
            False
          
        
        
          
            True
            True
            GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK
            0
            GTK_POLICY_AUTOMATIC
            GTK_POLICY_AUTOMATIC
            GTK_SHADOW_ETCHED_IN
            
                
                True
                True
                GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK
                2
                2
                GTK_WRAP_NONE
                
                
              
            
          
          
            1
          
        
        
          
            True
            GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK
            2
          
          
            False
            2
          
        
      
    
  

'''

# for the search ui, because it was easier this way.
SEARCH_UI = '''




    
    True


      


        True
        GTK_ORIENTATION_VERTICAL
        GTK_ORIENTATION_VERTICAL
        
          
            True
            
              
                True
                Find
                GTK_JUSTIFY_RIGHT
              
            
            
              
                True
                True
                
                
              
              
                1
              
            
          
        
        
          
            True
            
              
                True
                Replace
                GTK_JUSTIFY_RIGHT
              
            
            
              
                True
                True
                
                
              
              
                1
              
            
          
          
            1
          
        
        
          
            True
            True
            Whole word
            True
            
          
          
            2
          
        
        
          
            True
            True
            Case sensitive
            True
            
            
         
          
            3
          
        
        
          
            True
          
          
            False
            4
          
        
        
          
            True
            
              
                True
                True
                True
                Close
                
              
            
            
              
                True
                True
                True
                Find Previous
                
              
              
                1
              
            
            
              
                True
                True
                True
                Find Next
                
              
              
                2
              
            
            
              
                True
                True
                True
                Find & Replace
                
              
              
                3
              
            
          
          
            5
          
        
      
      
      

'''



import sys
import os
import imp,types,tempfile
import string
#import urlparse
import urllib
#import platform
import gio #this breaks windows
import gtk,gobject
import pango
import subprocess
import hashlib

from optparse import OptionParser


# prints about info from global vars to console
def print_about():
    print "\n%s %s\n%s\n%s"%(EDILE_NAME,EDILE_VERSION,EDILE_URL,EDILE_DESCRIPTION)

print_about()

try:
    import gtksourceview2
    using_source_view = True
    print "using gtksourceview"
except ImportError:
    using_source_view = False
    print "not using gtksourceview"
    pass



# names for text wrapping types
CONF_WRAP_MAP = {'None':gtk.WRAP_NONE,'Character':gtk.WRAP_CHAR, 'Word':gtk.WRAP_WORD, 'Word Character':gtk.WRAP_WORD_CHAR}

if using_source_view:
    # names for gtksourceview smart home end types
    CONF_SMART_HOME_END_TYPE_MAP =  {'Disabled':gtksourceview2.SMART_HOME_END_DISABLED,'Before':gtksourceview2.SMART_HOME_END_BEFORE,'After':gtksourceview2.SMART_HOME_END_AFTER,'Always':gtksourceview2.SMART_HOME_END_ALWAYS}

class Edile:

###
# on_* = action methods from ui
###

    # Called when the user clicks the 'About' menu. We use gtk_show_about_dialog()
    # which is a convenience function to show a GtkAboutDialog. This dialog will
    # NOT be modal but will be on top of the main application window.
    def on_about_menu_item_activate(self, menuitem, data=None):

        # show standard gtk about window
        def show_about():
            if self.about_dialog:
                self.about_dialog.present()
                return


            about_dialog = gtk.AboutDialog()
            about_dialog.set_transient_for(self.window)
            about_dialog.set_destroy_with_parent(True)
            about_dialog.set_name(EDILE_NAME)
            about_dialog.set_version(EDILE_VERSION)
            about_dialog.set_copyright("")
            about_dialog.set_website(EDILE_URL)
            about_dialog.set_comments(EDILE_DESCRIPTION)
            #about_dialog.set_authors(EDILE_AUTHORS)
            about_dialog.set_logo_icon_name(gtk.STOCK_EDIT)

            # callbacks for destroying the about dialog
            def close(dialog, response, editor):
                editor.about_dialog = None
                dialog.destroy()

            def delete_event(dialog, event, editor):
                editor.about_dialog = None
                return True

            about_dialog.connect("response", close, self)
            about_dialog.connect("delete-event", delete_event, self)

            self.about_dialog = about_dialog
            about_dialog.show()

        # run functions
        print_about()
        show_about()

    # view source. displays this file in a new instance.
    def on_view_source_menu_item_activate(self, menuitem, data=None):
        exe = __file__
        exepath = os.path.realpath(exe)
        self.spawn(exepath)

    # tries to run file in a terminal
    def on_execute_menu_item_activate(self,menuitem,data=None):
        exe = self.filename

        if exe == None:
            self.error_message("Save file first!")
            return

        if os.path.exists(exe):
            exepath = os.path.realpath(exe)
            subprocess.Popen(["xterm", "-hold", "-e", "\"%s\""%(exepath)])


    # When our window is destroyed, we want to break out of the GTK main loop.
    # We do this by calling gtk_main_quit(). We could have also just specified
    # gtk_main_quit as the handler in Glade!
    def on_window_destroy(self, widget, data=None):
        gtk.main_quit()

    # When the window is requested to be closed, we need to check if they have
    # unsaved work. We use this callback to prompt the user to save their work
    # before they exit the application. From the "delete-event" signal, we can
    # choose to effectively cancel the close based on the value we return.
    def on_window_delete_event(self, widget, event, data=None):
        if self.check_for_save():
            self.on_save_menu_item_activate(None, None)
            # if user cancelled save
            if self.filename == None: return True

        self.log('exiting')
        return False # Propogate event

    # Called when the user clicks the 'New' menu. We need to prompt for save if
    # the file has been modified, and then delete the buffer and clear the
    # modified flag.
    def on_new_menu_item_activate(self, menuitem, data=None):
        if self.check_for_save(): self.on_save_menu_item_activate(None, None)

        # clear editor for a new file
        buff = self.text_view.get_buffer()
        buff.set_text("")
        buff.set_modified(False)
        self.filename = None
	self.language = None
	self.text_encoding = None
        self.reset_default_status()

    def on_new_window_menu_item_activate(self, menuitem, data=None):
        self.spawn()

    # Called when the user clicks the 'Open' menu. We need to prompt for save if
    # thefile has been modified, allow the user to choose a file to open, and
    # then call load_file() on that file.
    def on_open_menu_item_activate(self, menuitem, data=None):
        if self.check_for_save(): self.on_save_menu_item_activate(None, None)

        filename = self.get_open_filename()
        if filename: self.load_file(filename)

    def on_open_in_new_window_menu_item_activate(self, menuitem, data=None):
        filename = self.get_open_filename()
        if filename: self.spawn(filename)


    # Called when the user clicks the 'Save' menu. We need to allow the user to choose
    # a file to save if it's an untitled document, and then call write_file() on that
    # file.
    def on_save_menu_item_activate(self, menuitem, data=None):
        if self.filename == None:
            filename = self.get_save_filename()
            if filename: self.write_file(filename)
        else: self.write_file(None)

    # Called when the user clicks the 'Save As' menu. We need to allow the user
    # to choose a file to save and then call write_file() on that file.
    def on_save_as_menu_item_activate(self, menuitem, data=None):
        filename = self.get_save_filename()
        if filename: self.write_file(filename)

    def on_close_menu_item_activate(self, menuitem, data=None):
        if self.check_for_save():
            self.on_save_menu_item_activate(None, None)
            # if user cancelled save
            if self.filename == None: return
        self.text_view.set_sensitive(False)
        buff = self.text_view.get_buffer()
        buff.set_text("")
        buff.set_modified(False)
        self.text_view.set_sensitive(True)
        self.filename = None
        self.language = None
        self.reset_default_status()

    # Offer to save any changes, then reload original file from disk
    def on_reload_menu_item_activate(self, menuitem, data=None):
        old_filename = self.filename
        if self.check_for_save():
            self.on_save_as_menu_item_activate(menuitem, None)
            # if user cancelled save
            if self.filename == None: return

        self.log('reloading %s'%(old_filename))
        self.load_file(old_filename)

    # Called when the user clicks the 'Quit' menu. We need to prompt for save if
    # the file has been modified and then break out of the GTK+ main loop
    def on_quit_menu_item_activate(self, menuitem, data=None):
        if self.check_for_save():
            self.on_save_menu_item_activate(None, None)
            # if user cancelled save
            if self.filename == None: return

        self.log('exiting')
        gtk.main_quit()

    def on_undo_menu_item_activate(self, menuitem, data=None):
        if using_source_view:
            if self.text_view.get_buffer().can_undo():
                self.text_view.get_buffer().undo()

    def on_redo_menu_item_activate(self, menuitem, data=None):
        if using_source_view:
            if self.text_view.get_buffer().can_redo():
                self.text_view.get_buffer().redo()
    # Called when the user clicks the 'Cut' menu.
    def on_cut_menu_item_activate(self, menuitem, data=None):
        buff = self.text_view.get_buffer();
        buff.cut_clipboard (gtk.clipboard_get(), True);

    # Called when the user clicks the 'Copy' menu.
    def on_copy_menu_item_activate(self, menuitem, data=None):
        buff = self.text_view.get_buffer();
        buff.copy_clipboard (gtk.clipboard_get());

    # Called when the user clicks the 'Paste' menu.
    def on_paste_menu_item_activate(self, menuitem, data=None):
        buff = self.text_view.get_buffer();
        buff.paste_clipboard (gtk.clipboard_get(), None, True);

    def on_select_all_menu_item_activate(self, menuitem, data=None):
        buff = self.text_view.get_buffer();
        buff.select_range(buff.get_start_iter(),buff.get_end_iter())

    # Called when the user clicks the 'Delete' menu.
    def on_delete_menu_item_activate(self, menuitem, data=None):
        buff = self.text_view.get_buffer();


        gone = buff.get_text(buff.get_selection_bounds()[0],buff.get_selection_bounds()[1])
        self.log('deleting %s'%(gone))

        buff.delete_selection (False, True);

    def on_find_prev_selected_menu_item_activate(self,menuitem,data=None):

        buff = self.text_view.get_buffer()
        selected = buff.get_text(buff.get_selection_bounds()[0],buff.get_selection_bounds()[1])

        iter = self.get_find_iter(buffer=buff, backwards=True,limit_iter=None)

        results = self.do_find_with_iter(iter=iter, search_for=selected,backwards=True,case_sensitive=False,whole_word=False)

        try:
            buff.select_range(results[0],results[1])
            self.text_view.scroll_to_mark(mark=buff.get_insert(),within_margin=0.33,use_align=True,xalign=0.5,yalign=0.5)
        except:
            self.log("\'%s\' not found."%(selected))

    def on_find_next_selected_menu_item_activate(self,menuitem,data=None):
        buff = self.text_view.get_buffer()
        selected = buff.get_text(buff.get_selection_bounds()[0],buff.get_selection_bounds()[1])

        iter = self.get_find_iter(buffer=buff, backwards=False,limit_iter=None)

        results = self.do_find_with_iter(iter=iter, search_for=selected,backwards=False,case_sensitive=True,whole_word=False)

        try:
            buff.select_range(results[0],results[1])
            self.text_view.scroll_to_mark(mark=buff.get_insert(),within_margin=0.33,use_align=True,xalign=0.5,yalign=0.5)
        except:
            self.log("\'%s\' not found."%(selected))

    def on_search_case_sensitive_checkbox_toggled(self, data=None):
        #self.search_case_sensitive = togglebutton.get_active()
        self.search_case_sensitive = not self.search_case_sensitive


    def on_search_whole_word_checkbox_toggled(self, data=None):
        #self.search_is_whole_word = togglebutton.get_active()
        self.search_is_whole_word = not self.search_is_whole_word

    def search_find_field_changed(self, data=None):
        self.search_string = self.search_field.get_text()
    def search_replace_field_changed(self, data=None):
        self.replacement_string = self.replace_field.get_text()

    def on_show_find_menu_item_activate(self,menuitem,data=None):
        #TODO: could load search UI from xml string here

        if self.search_window.get_property("visible"):
            self.search_window.present()
            return

        buff = self.text_view.get_buffer()
        if self.search_string == "":
            if buff.get_selection_bounds():
                self.search_string = buff.get_slice(buff.get_selection_bounds()[0],buff.get_selection_bounds()[1])

        self.search_field.set_text(self.search_string)
        self.replace_field.set_text(self.replacement_string)
        self.search_field.grab_focus()

        self.search_window.show_all()

    def on_search_window_delete_event(self, widget=None, event=None, data=None):
        self.search_window.hide_all()
        return True

    def on_find_next_menu_item_activate(self, sender, data=None):
        buff = self.text_view.get_buffer();
        if self.search_string == "":
            if buff.get_selection_bounds():
                self.search_string = buff.get_slice(buff.get_selection_bounds()[0],buff.get_selection_bounds()[1])
                self.search_field.set_text(self.search_string)
            else:
                self.error_message("Enter the search string first.")
                self.on_show_find_menu_item_activate(sender)

        next_results = self.do_find(buffer=buff,iter=None,backwards=False,case_sensitive=self.search_case_sensitive,whole_word=self.search_is_whole_word)

        #highlight next results
        #offer to wrap

    #find backwards
    def on_find_previous_menu_item_activate(self, menuitem, data=None):
        buff = self.text_view.get_buffer();
        if self.search_string == "":
            if buff.get_selection_bounds():
                self.search_string = buff.get_slice(buff.get_selection_bounds()[0],buff.get_selection_bounds()[1])
                self.search_field.set_text(self.search_string)
            else:
                self.error_message("Enter the search string first.")
                self.on_show_find_menu_item_activate(menuitem)

        prev_results = self.do_find(buffer=buff,iter=None,backwards=True,case_sensitive=self.search_case_sensitive,whole_word=self.search_is_whole_word)

        #highlight prev results
        #offer to wrap

    def on_find_all_menu_item_activate(self, menuitem, data=None):
        buff = self.text_view.get_buffer();
        if self.search_string == "":
            if buff.get_selection_bounds():
                self.search_string = buff.get_slice(buff.get_selection_bounds()[0],buff.get_selection_bounds()[1])
                self.search_field.set_text(self.search_string)
            else:
                self.error_message("Enter the search string first.")
                self.on_show_find_menu_item_activate(menuitem)
                return
        self.do_find_all(buffer=buff)

    def on_replace_and_find_menu_item_activate(self, menuitem, data=None):
        if self.search_string == "":
            self.error_message("Enter the search string first.")
            return
        if self.replacement_string == '':
            self.error_message("Enter the replacement string first.")
            self.on_show_find_menu_item_activate(menuitem)
            return

        buff = self.text_view.get_buffer()

        replace_iter = self.get_find_iter(buffer=buff, backwards=buff.get_has_selection(),limit_iter=None) #LAST

        next_results = self.do_find(buffer=buff,iter=replace_iter,backwards=False,case_sensitive=self.search_case_sensitive,whole_word=self.search_is_whole_word)

        if next_results == None:
            return
        else:
            result_iter = next_results[0]
            #ask for confirm here
            self.do_replace_next(buffer=buff,iter=result_iter,search_for=self.search_string,replace_with=self.replacement_string,limit=next_results[1])

    def on_replace_all_menu_item_activate(self, menuitem, data=None):
        if self.search_string == "":
            self.error_message("Enter the search string first.")
            return
        if self.replacement_string == "":
            self.error_message("Enter the replacement string first.")
            return

        buff = self.text_view.get_buffer()
        self.do_replace_all(buff,self.search_string,self.replacement_string)


    # Called when the user clicks an item from the 'Wrap' menu.
    def on_wrap_none_menu_item_activate(self, menuitem, data=None):
        self.wrapping = 'None'
        self.text_view.set_wrap_mode(gtk.WRAP_NONE);

    def on_wrap_char_menu_item_activate(self, menuitem, data=None):
        self.wrapping = 'Character'
        self.text_view.set_wrap_mode(gtk.WRAP_CHAR);

    def on_wrap_word_menu_item_activate(self, menuitem, data=None):
        self.wrapping = 'Word'
        self.text_view.set_wrap_mode(gtk.WRAP_WORD);

    def on_select_font_menu_item_activate(self,menuitem, data=None):
        if not self.font_dialog:
            window = gtk.FontSelectionDialog("Font Selection Dialog")
            self.font_dialog = window

            window.set_position(gtk.WIN_POS_MOUSE)
            window.set_transient_for(self.window)
            window.connect("destroy", self.font_dialog_destroyed)

            window.ok_button.connect("clicked", self.font_selection_ok)
            window.cancel_button.connect_object("clicked", lambda wid: wid.destroy(), self.font_dialog)
            current_font = self.text_view.get_pango_context().get_font_description().to_string()
            window.set_font_name(current_font)

        window = self.font_dialog
        if not (window.flags() & gtk.VISIBLE):
            window.show()
        else:
            window.destroy()
            self.font_dialog = None

    def font_selection_ok(self, button):
        self.font = self.font_dialog.get_font_name()
        if self.window:
            font_desc = pango.FontDescription(self.font)
            if font_desc:
                self.text_view.modify_font(font_desc)
                self.font_dialog.destroy()
    def font_dialog_destroyed(self, data=None):
        self.font_dialog = None

    #emitted when the modified flag of the buffer changes
    def on_text_buffer_modified_changed(self, buff, data=None):
        self.mark_window_status(self.window,buff.get_modified())

    #emitted BEFORE the text is actually inserted into the buffer
    def on_text_buffer_insert_text(self, buff, iter, text, length, data=None):
        # for change highlighting. just save the edit location and length here.
        # the actual highlight is applied in on_text_buffer_changed()
        self.edit_loc = iter.get_offset()
        self.edit_len = length


        # remove any find highlighting we've done
        if buff.get_tag_table().lookup("search_results"):
            buff.remove_tag_by_name('search_results', buff.get_start_iter(), buff.get_end_iter())


    #emitted AFTER text is inserted into the buffer
    #here we retrieve the location and apply the highlight tag
    def on_text_buffer_changed(self, buff,data=None):

        # we set edit_loc to -1 on deletions
        if self.edit_loc >=0:
            start_iter = buff.get_iter_at_offset(self.edit_loc)
            start_mark = buff.create_mark(None,start_iter)
            end_iter = buff.get_iter_at_offset(self.edit_loc + self.edit_len)
            end_mark = buff.create_mark(None,end_iter)
            self.mark_range_as_changed(buff,start_mark,end_mark)
            buff.delete_mark(start_mark)
            buff.delete_mark(end_mark)

    #set the edit location to -1 here so on_text_buffer_changed() doesn't
    #try to set attributes on a deleted range
    def on_text_buffer_delete_range(self,buff,start,end, data=None):
        self.edit_loc = -1


    #def on_text_buffer_mark_set(self, textbuffer, iter, textmark, data=None):
    #    if textmark.get_name() == 'insert':
    #        self.move_replace_cursor(textbuffer,iter)

    # We call error_message() any time we want to display an error message to
    # the user. It will both show an error dialog and log the error to the
    # terminal window.
    def error_message(self, message):
        # log to terminal window
        self.log(message)

        # create an error message dialog and display modally to the user
        dialog = gtk.MessageDialog(None,
                                   gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
                                   gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, message)

        dialog.run()
        dialog.destroy()

    # This function will check to see if the text buffer has been
    # modified and prompt the user to save if it has been modified.
    def check_for_save (self):
        ret = False
        buff = self.text_view.get_buffer()

        if buff.get_modified():

            # we need to prompt for save
            message = "Do you want to save the changes you have made?"
            dialog = gtk.MessageDialog(self.window,
                                       gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
                                       gtk.MESSAGE_QUESTION, gtk.BUTTONS_YES_NO,
                                       message)
            dialog.set_title("Save?")

            if dialog.run() == gtk.RESPONSE_NO: ret = False
            else: ret = True

            dialog.destroy()

        return ret

    # We call get_open_filename() when we want to get a filename to open from the
    # user. It will present the user with a file chooser dialog and return the
    # filename or None.
    def get_open_filename(self):
        filename = None
        chooser = gtk.FileChooserDialog("Open File...", self.window,
                                        gtk.FILE_CHOOSER_ACTION_OPEN,
                                        (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
                                         gtk.STOCK_OPEN, gtk.RESPONSE_OK))

        response = chooser.run()
        if response == gtk.RESPONSE_OK: filename = chooser.get_filename()
        chooser.destroy()

        return filename

    # We call get_save_filename() when we want to get a filename to save from the
    # user. It will present the user with a file chooser dialog and return the
    # filename or None.
    def get_save_filename(self):
        filename = None
        chooser = gtk.FileChooserDialog("Save File...", self.window,
                                        gtk.FILE_CHOOSER_ACTION_SAVE,
                                        (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
                                         gtk.STOCK_SAVE, gtk.RESPONSE_OK))

        response = chooser.run()
        if response == gtk.RESPONSE_OK: filename = chooser.get_filename()
        #if response == gtk.RESPONSE_CANCEL: print "save cancelled"
        chooser.destroy()

        return filename



###
# do_* = controller methods called from on_* actions
###

    # find

    def get_find_iter(self,buffer=None, backwards=False,limit_iter=None):
        find_iter = None
        selection = buffer.get_selection_bounds()
        if selection == ():
            find_iter = buffer.get_iter_at_mark(buffer.get_insert())
            if find_iter == None:
                find_iter = buffer.get_bounds()[int(backwards)]
        else:
            find_iter = selection[int(not backwards)]

        return find_iter


    def do_find(self,buffer=None,iter=None,backwards=False,case_sensitive=False,whole_word=False):

        if self.search_string == "":
            # show the ui
            self.on_show_find_menu_item_activate(buffer)
            return

        find_iter = iter

        if find_iter == None:
            find_iter = self.get_find_iter(buffer=buffer, backwards=backwards,limit_iter=None)

        results = self.do_find_with_iter(find_iter, self.search_string, backwards=backwards, case_sensitive=case_sensitive,whole_word=whole_word)

        if results == None:
            #nothing else found. ask to wrap.
            self.log( "\'%s\' not found"%(self.search_string))
            if self.ask_for_wrap(backwards=backwards):
                self.log("wrapping")
                wrapped_iter = buffer.get_bounds()[int(backwards)]
                results = self.do_find_with_iter(wrapped_iter, self.search_string, backwards=backwards, case_sensitive=case_sensitive,whole_word=whole_word)
                if results:
                    ins, bound = results
                    buffer.select_range(ins,bound)
                    self.text_view.scroll_to_mark(mark=buffer.get_insert(),within_margin=0.33,use_align=True,xalign=0.5,yalign=0.5)
                else:
                    # really not found. beep.
                    gtk.gdk.beep()
        else:
            #happily select and highlight
            ins, bound = results
            buffer.select_range(ins,bound)
            self.text_view.scroll_to_mark(mark=buffer.get_insert(),within_margin=0.33,use_align=True,xalign=0.5,yalign=0.5)

        return results

    def do_find_with_iter(self, iter=None, search_for=None,backwards=False,case_sensitive=False,whole_word=False):

        def next_match():
            results = None
            search_flags = 0
            if using_source_view:
                if not self.search_case_sensitive:
                    search_flags = gtksourceview2.SEARCH_CASE_INSENSITIVE
                if backwards:
                    results = gtksourceview2.iter_backward_search(iter, search_for, flags=search_flags)#, limit=limit_iter)
                else:
                    results = gtksourceview2.iter_forward_search(iter, search_for, flags=search_flags)#, limit=limit_iter)
            else:
                if backwards:
                    results = iter.backward_search(search_for, flags=search_flags)#, limit=limit_iter)
                else:
                    results = iter.forward_search(search_for, flags=search_flags)#, limit=limit_iter)
            return results

        #test for whole word , case
        def next_whole_word():
            word_hit = None
            whole_word_hit = False
            while not whole_word_hit:
                word_hit = next_match()
                if word_hit:
                    if word_hit[0].starts_word() and word_hit[1].ends_word():
                        #hit = result
                        whole_word_hit = True
                        return word_hit
                    else:
                        if not backwards:
                            iter.forward_chars(len(search_for))
                        else:
                            iter.backward_chars(len(search_for))
                        #print iter.get_offset()
                        word_hit = next_match()
                else:
                    return None

            return word_hit

        result = next_match()

        # test substring result for conditions
        if self.search_is_whole_word:
            hit = next_whole_word()
        else:
            hit = result

        return hit

    def ask_for_wrap(self,backwards=False):
        ret = False

        direction = 'beginning'
        if backwards:
            direction = 'end'

        message = "\'%s\' not found. Wrap and start from %s of document?"%(self.search_string,direction)
        dialog = gtk.MessageDialog(self.window,
                                   gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
                                   gtk.MESSAGE_QUESTION, gtk.BUTTONS_YES_NO,
                                   message)

        dialog.set_title("Wrap Search?")

        if dialog.run() == gtk.RESPONSE_NO: ret = False
        else: ret = True

        dialog.destroy()

        return ret

    def ask_for_replace(self):
        ret = False

        message = "Replace?"
        dialog = gtk.MessageDialog(self.window,
                                       gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
                                       gtk.MESSAGE_QUESTION, gtk.BUTTONS_YES_NO,
                                       message)
        dialog.set_title("Replace?")

        if dialog.run() == gtk.RESPONSE_NO: ret = False
        else: ret = True

        dialog.destroy()

        return ret

    def do_find_all(self,buffer=None,case_sensitive=False,whole_word=False):
        start_iter = buffer.get_start_iter()
        end_iter = buffer.get_end_iter()

        begin_mark = buffer.create_mark(None,start_iter)
        end_mark = buffer.create_mark(None,end_iter)
        search_results = self.find_all_in_range(buffer, self.search_string, begin_mark,end_mark)
        buffer.delete_mark(begin_mark)
        buffer.delete_mark(end_mark)

        if not buffer.get_tag_table().lookup("search_results"):
            preview_tag = buffer.create_tag("search_results", background_set="True",background=CONF_FIND_HIGHLIGHT)
        else:
            buffer.remove_tag_by_name('search_results', start_iter, end_iter)

        if search_results:
            for range in search_results:
                if self.search_is_whole_word:
                    if range[0].starts_word() and range[1].ends_word():
                        buffer.apply_tag_by_name("search_results",range[0],range[1])
                else:
                    buffer.apply_tag_by_name("search_results",range[0],range[1])


    def do_replace_next(self,buffer=None,iter=None,search_for='',replace_with='',limit=None):

        buffer.remove_tag_by_name('search_results', buffer.get_start_iter(), buffer.get_end_iter())

        replace_iter = iter

        range = self.do_find(buffer=buffer,iter=replace_iter,backwards=False,case_sensitive=self.search_case_sensitive,whole_word=self.search_is_whole_word)

        if range:
            if not buffer.get_tag_table().lookup("search_results"):
                preview_tag = buffer.create_tag("search_results", background_set="True",background=CONF_FIND_HIGHLIGHT)

            #apply tag
            buffer.apply_tag_by_name("search_results",range[0],range[1])

            if not self.ask_for_replace():
                self.log("replace declined")
                return

            replace_begin = buffer.create_mark(None,range[0])
            replace_end = buffer.create_mark(None,range[1])
            replace_list = [(replace_begin,replace_end)]


            replaced_range = self.replace_text(buffer,replace_with,replace_list)

            self.text_view.scroll_to_mark(replace_end,0)
            buffer.delete_mark(replace_begin)
            buffer.delete_mark(replace_end)


    def do_replace_all(self,buffer,search_for,replace_with):
        #search_results = self.search_for_text(buffer,search_for,buffer.get_start_iter(),buffer.get_end_iter())
        replace_begin = buffer.create_mark(None,buffer.get_start_iter())
        replace_end = buffer.create_mark(None,buffer.get_end_iter())
        search_results = self.find_all_in_range(buffer,search_for,replace_begin,replace_end)

        result_marks = []
        for iter_pair in search_results:
            this_mark_pair = []
            for iter in iter_pair:
                this_mark = buffer.create_mark(None,iter)
                this_mark_pair.append(this_mark)
            self.edit_loc = iter_pair[0]#wft

            result_marks.append(this_mark_pair)

        self.replace_text(buffer,replace_with,result_marks)

        for that_mark_pair in result_marks:
            for that_mark in that_mark_pair:
                buffer.delete_mark(that_mark)

        buffer.delete_mark(replace_begin)
        buffer.delete_mark(replace_end)


    def find_all_in_range(self, buff, search_string, start_mark, end_mark):
        start_iter = buff.get_iter_at_mark(start_mark)
        end_iter = buff.get_iter_at_mark(end_mark)
        search_results = []

        next_result = self.do_find_with_iter(iter=start_iter,search_for=self.search_string,backwards=False,case_sensitive=self.search_case_sensitive,whole_word=self.search_is_whole_word)
        while next_result:
            
		

Tags: file, gtk, linux, python, scripts

Rate This Article:



Privacy Policy | Copyright/Trademark Notification