# Purpose: Setup and initialize the core gui interfaces of the editpane
#
# $Id: editpane.rb,v 1.46 2005/12/16 11:11:40 jonathanm Exp $
#
# Authors: Curt Hibbs <curt@hibbs.com>
# Laurent Julliard <laurent AT moldus DOT org>
# Richard Kilmer <rich AT infoether DOT com>
# Contributors:
#
# This file is part of the FreeRIDE project
#
# This application is free software; you can redistribute it and/or
# modify it under the terms of the Ruby license defined in the
# COPYING file.
#
# Copyright (c) 2001 Curt Hibbs. All rights reserved.
# Copyright (c) 2002-2003 Laurent Julliard. All rights reserved.
# Copyright (c) 2002-2003 Rich Kilmer. All rights reserved.
#
begin
require 'rubygems'
require_gem 'fxruby', '>= 1.2.0'
rescue LoadError
require 'fox12'
end
require 'fox12/responder'
require 'fox12/colors'
require 'rubyide_fox_gui/fxscintilla/scintilla'
require 'rubyide_fox_gui/editpane_configurator'
require 'rubyide_gui/code_template'
module FreeRIDE
module FoxRenderer
include Fox
include FreeRIDE::Objects
##
# This is the module that renders ediutpanes using
# FXScintilla.
#
class EditPane
include Fox
ICON_PATH = "/system/ui/icons/EditPane"
extend FreeBASE::StandardPlugin
def EditPane.start(plugin)
edit_book_slot = plugin["/system/ui/fox/editBook"]
component_slot = plugin["/system/ui/components/EditPane"]
# Subscribe to the editpane slot to render any newly created edit pane
component_slot.subscribe do |event, slot|
if (event == :notify_slot_add && slot.parent == component_slot)
if (edit_book_slot.data == nil)
# create (only once) a TabBook in which all editor windows will appear
contentFrame = plugin["/system/ui/fox/contentFrame"].data
tb = FXTabBook.new(contentFrame, nil, 0,LAYOUT_FILL_X|LAYOUT_FILL_Y)
tb.create
edit_book_slot.data = tb
# catch selected tabitems on the tabbook (scn is the panel number 0,1,2...)
tb.connect(SEL_COMMAND) do |sender, sel, scn|
editpane_slot = tb.childAtIndex(2*scn).userData
editpane_slot.manager.make_current
end
end
Renderer.new(plugin, slot)
end
end
# If a new icon is invoked through its slot then autoload the icon
plugin[ICON_PATH].subscribe do |event, slot|
if event == :notify_slot_add
app = slot['/system/ui/fox/FXApp'].data
path = "#{plugin.plugin_configuration.full_base_path}/icons/#{slot.name}.png"
plugin.log_info << "iconpath : #{path}"
if FileTest.exist?(path)
slot.data = FXPNGIcon.new(app, File.open(path, "rb").read)
slot.data.create
end
end
end
# Create the Goto Line command
cmd_mgr = plugin["/system/ui/commands"].manager
cmdg = cmd_mgr.add("EditPane/GotoLine", "Line...") do |cmd_slot|
GotoLineDialog.new(plugin)
end
plugin["/system/ui/keys"].manager.bind("EditPane/GotoLine", :ctrl, :G)
# Create the Find EditPane command
cmdf = cmd_mgr.add("EditPane/Find", "&Find...") do |cmd_slot|
sd = FindDialog.new(plugin)
sd.execute
end
plugin["/system/ui/keys"].manager.bind("EditPane/Find", :ctrl, :F)
# Create the Find/Replace command
cmdr = cmd_mgr.add("EditPane/Replace", "&Replace...") do |cmd_slot|
rd = ReplaceDialog.new(plugin)
rd.execute
end
plugin["/system/ui/keys"].manager.bind("EditPane/Replace", :ctrl, :R)
# Create the Find Next command
cmdfn = cmd_mgr.add("EditPane/FindNext", "Find &next") do |cmd_slot|
fd = FindDialog.new(plugin)
fd.onCmdNext(nil,nil,nil)
end
plugin["/system/ui/keys"].manager.bind("EditPane/FindNext", :F3)
# Create the Find Prev command
cmdfp = cmd_mgr.add("EditPane/FindPrev", "Find pre&vious") do |cmd_slot|
fd = FindDialog.new(plugin)
fd.onCmdPrev(nil,nil,nil)
end
plugin["/system/ui/keys"].manager.bind("EditPane/FindPrev", :shift, :F3)
# Create the Code Templates... command
cmdct = cmd_mgr.add("EditPane/CodeTemplates", "Code &Templates...") do |cmd_slot|
ep_slot = plugin['/system/ui/current/EditPane']
ep_slot.manager.code_completion()
end
plugin["/system/ui/keys"].manager.bind("EditPane/CodeTemplates", :ctrl, :J)
# Create the Code Templates... command
#cmdca = cmd_mgr.add("EditPane/CodeAssist", "Code &Assist...") do |cmd_slot|
# ep_slot = plugin['/system/ui/current/EditPane']
# ep_slot.manager.code_assist()
#end
#plugin["/system/ui/keys"].manager.bind("EditPane/CodeAssist", :ctrl, :K)
# Command availability subject to same behavior for all three
[cmdg,cmdf,cmdr,cmdfn,cmdfp,cmdct].each do |cmd|
cmd.availability = plugin['/system/ui/current'].has_child?('EditPane')
cmd.manage_availability do |command|
plugin['/system/ui/current'].subscribe do |event, slot|
if slot.name=="EditPane"
case event
when :notify_slot_link
command.availability=true
when :notify_slot_unlink
command.availability=false
end
end
end
end
end
# Insert the "goto line..." menu item in the Goto menu
gotomenu = plugin["/system/ui/components/MenuPane/Goto_menu"].manager
gotomenu.add_command("EditPane/GotoLine")
# Insert the "Find..." and "Replace..." menu item in the Search menu
searchmenu = plugin["/system/ui/components/MenuPane/Search_menu"].manager
searchmenu.add_command("EditPane/Find")
searchmenu.add_command("EditPane/Replace")
searchmenu.add_command("EditPane/FindNext")
searchmenu.add_command("EditPane/FindPrev")
# Insert the "Code Templates..." menu item in the Edit menu
editmenu = plugin["/system/ui/components/MenuPane/Edit_menu"].manager
editmenu.add_command("EditPane/CodeTemplates")
#editmenu.add_command("EditPane/CodeAssist")
# Initialize configurator UI for the editor - Must be done
# before any editpane is created
EditPaneConfiguratorRenderer.new(plugin)
# Update the status of some checked menu items
cmd_mgr.command('App/View/Whitespace').checked = plugin.properties['white_space']
cmd_mgr.command('App/View/EndOfLine').checked = plugin.properties['eol']
cmd_mgr.command('App/View/LineNumbers').checked = plugin.properties['line_numbers']
# Restore files edited in the previous session
component_slot.each_slot do |slot|
slot.notify(:notify_slot_add)
slot.manager.load_file(slot.data)
slot.notify(:notify_data_set)
slot.manager.make_current
end
# Also load any file appearing on the command line
ARGV.each do |filename|
# see if it has already been loaded
loaded = false
plugin['/system/ui/components/EditPane'].each_slot do |ep_slot|
if ep_slot.data == filename
loaded = true
break
end
end
unless loaded
ep_slot = plugin['/system/ui/components/EditPane'].manager.add
ep_slot.manager.load_file(filename)
end
end
# Make the first editpane the current one
component_slot.each_slot do |slot|
slot.manager.make_current
break
end
# Now only is this plugin running
plugin.transition(FreeBASE::RUNNING)
end
class Renderer
include Fox
attr_reader :plugin
def initialize(plugin, slot)
@plugin = plugin
@slot = slot
@app = plugin['/system/ui/fox/FXApp'].data
@icons = plugin[ICON_PATH]
@edit_book = plugin["/system/ui/fox/editBook"].data
@tab = FXTabItem.new(@edit_book, slot.data, nil, TAB_TOP_NORMAL)
@tab.iconPosition = ICON_AFTER_TEXT
@frame = FXHorizontalFrame.new(@edit_book, FRAME_THICK|LAYOUT_FILL_X|LAYOUT_FILL_Y)
@scintilla = FXScintilla.new(@frame, nil, 0, LAYOUT_FILL_X|LAYOUT_FILL_Y)
@tab.create
@tab.connect(SEL_FOCUSIN) {@scintilla.setFocus}
@frame.create
@scintilla.create
# attach the slot to the 2 tabitem widget and the renderer to the
# scintilla controller s so that they both know to which higher
# level object they belong to
@tab.userData = @slot
@scintilla.userData = self
@controller = ScintillaController.new(@scintilla)
@scintilla.connect(SEL_COMMAND) do |sender, sel, scn|
@controller.handle_notification(scn.nmhdr.idFrom, scn.nmhdr.code, scn)
end
@controller.setup
setup_actions
slot.subscribe do |event, slot|
update(event) if ((event == :refresh) || (event == :notify_data_set))
end
# Apply user-defined editor preferences
plugin['/plugins/rubyide_fox_gui-editpane/configurator'].manager.apply_all_config(@slot)
@plugin.log_info << "EditPane created"
end
def update(event)
if (event == :notify_data_set)
# the data slot holds the file name so update the tabItem
if (@slot.data)
@tab.text = File.basename(@slot.data)
else
@tab.text = 'Untitled'
end
end
end
def setup_actions
bind_action("filename", :filename)
bind_action("load_file", :load_file)
bind_action("make_current", :make_current)
bind_action("close", :close)
bind_action("save", :save)
bind_action("neighbor", :neighbor)
bind_action("modified", :modified?)
bind_action("undo", :undo)
bind_action("redo", :redo_cmd)
bind_action("cut", :cut)
bind_action("copy", :copy)
bind_action("paste", :paste)
bind_action("show_errorline", :show_errorline)
bind_action("clear_errorline", :clear_errorline)
bind_action("show_debugline", :show_debugline)
bind_action("clear_debugline", :clear_debugline)
bind_action("get_cursor_line", :cursor_line)
bind_action("set_cursor_line", :set_cursor_line)
bind_action("get_text", :get_text)
bind_action("get_text_length", :text_length)
bind_action("help_lookup", :help_lookup)
bind_action("code_completion", :code_completion)
bind_action("get_ext_object", :get_ext_object)
bind_action("is_whitespace_visible", :is_whitespace_visible?)
bind_action("whitespace_visible", :whitespace_visible=)
bind_action("is_eol_visible", :is_eol_visible?)
bind_action("eol_visible", :eol_visible=)
bind_action("are_linenumbers_visible", :are_linenumbers_visible?)
bind_action("linenumbers_visible", :linenumbers_visible=)
bind_action("are_indentation_guides_visible", :are_indentation_guides_visible?)
bind_action("indentation_guides_visible", :indentation_guides_visible=)
bind_action("set_caret_period", :caret_period=)
bind_action("get_caret_period", :caret_period)
bind_action("set_caret_fore", :caret_fore=)
bind_action("set_caret_width", :caret_width=)
bind_action("get_wrap_mode", :wrap_mode)
bind_action("set_code_folding", :code_folding=)
bind_action("get_code_folding", :code_folding)
bind_action("get_editor_font", :editor_font=)
bind_action("set_editor_font", :editor_font)
bind_action("set_style", :set_style)
bind_action("set_style_clear_all", :set_style_clear_all)
bind_action("get_project", :get_project)
bind_action("set_project", :project=)
end
def bind_action(name, meth)
@slot["actions/#{name}"].set_proc method(meth)
end
### Commands ###
def get_project
@project
end
def project=(prj)
@project = prj
end
def get_ext_object
@controller.model
end
def filename
@slot.manager.filename
end
def load_file(filename, breakpoints=nil)
loaded = false
begin
@app.beginWaitCursor()
begin
@controller.open(filename)
rescue
cmd_mgr = @plugin["/system/ui/commands"].manager
cmd_mgr.command('App/Services/MessageBox').invoke("File Load Error!",$!.to_s.wrap(60))
else
if breakpoints
breakpoints.each {|line| @controller.toggle_breakpoint(line-1, false)}
end
loaded = true
end
ensure
@app.endWaitCursor()
end
return loaded
end
def make_current
index = @edit_book.indexOfChild(@tab)
@edit_book.setCurrent(index/2)
@edit_book.childAtIndex(index+1).setFocus()
@scintilla.setFocus()
end
def close
# FIXME? : how to delete an item in the tabbook? I'm not sure
# for the moment I remove child 2*N+1 first (the scintilla pane) and then
# the 2*N (the tabitem) second. It works.
@tab.hide()
@frame.removeChild(@scintilla)
@edit_book.removeChild(@frame)
@edit_book.removeChild(@tab)
@tab = nil
@frame = nil
@tab = nil
end
def save(filename)
begin
@app.beginWaitCursor()
begin
@controller.save(filename)
rescue
cmd_mgr = @plugin["/system/ui/commands"].manager
cmd_mgr.command('App/Services/MessageBox').invoke("File Save Error!",$!.to_s.wrap(60))
end
ensure
@app.endWaitCursor()
end
end
def neighbor
tab_index = @edit_book.indexOfChild(@tab)
if (tab_index == 0)
if @edit_book.numChildren > 2
tab_index += 2
else
return nil
end
else
tab_index -= 2
end
@edit_book.childAtIndex(tab_index).userData
end
def modified?
@controller.modified?
end
def undo
@controller.undo
end
def redo_cmd
@controller.redo
end
def cut
@controller.cut
end
def is_eol_visible?
@controller.is_eol_visible?
end
def eol_visible=(value)
@controller.eol_visible = value
end
def is_whitespace_visible?
@controller.is_whitespace_visible?
end
def whitespace_visible=(value)
@controller.whitespace_visible=value
end
def are_linenumbers_visible?
@controller.are_linenumbers_visible?
end
def linenumbers_visible=(value)
@controller.linenumbers_visible=value
end
def are_indentation_guides_visible?
@controller.are_indentation_guides_visible?
end
def indentation_guides_visible=(value)
@controller.indentation_guides_visible=value
end
def is_eol_visible?
@controller.is_eol_visible?
end
def caret_period
@controller.caret_period
end
def caret_period=(msec)
@controller.caret_period = msec
end
def caret_fore
self.get_ext_object.get_caret_fore
end
def caret_fore=(rgba)
self.get_ext_object.set_caret_fore(rgba&0xFFFFFF)
end
def caret_width
self.get_ext_object.get_caret_width
end
def caret_width=(width)
self.get_ext_object.set_caret_width(width)
end
def wrap_mode
@controller.wrap_mode
end
def wrap_mode=(status)
@controller.wrap_mode = status
end
def code_folding
@controller.code_folding
end
def code_folding=(status)
@controller.code_folding = status
end
def editor_font
@controller.editor_font
end
def editor_font=(font)
@controller.editor_font = font
end
def set_style(style_name, style)
@controller.set_style(style_name,style)
end
def set_style_clear_all
@controller.set_style_clear_all
end
def copy
@controller.copy
end
def paste
@controller.paste
end
def show_errorline(line)
@controller.show_errorline(line)
end
def clear_errorline
@controller.show_errorline(nil)
end
def show_debugline(line)
@controller.show_debugline(line)
end
def clear_debugline
@controller.show_debugline(nil)
end
def cursor_line
@controller.cursor_line+1
end
def set_cursor_line(line)
@controller.cursor_line = line-1
end
def get_text
@controller.get_text
end
def text_length
@controller.text_length
end
def help_lookup
@controller.help_lookup
end
##
# called from the scintilla controller whenever the modified
# status flag is updated
#
#
# Return:: [Boolean] true if it debugger paused
#
def modified=(flag)
if flag
icon = @icons['modified'].data
@tab.icon = icon
else
@tab.setIcon(nil)
end
end
##
# add a breakpoint on the line
# called from the scintilla controller
#
def add_breakpoint(line)
@slot.manager.add_breakpoint(line)
end
##
# delete a breakpoint on the line
# called from the scintilla controller
#
def delete_breakpoint(line)
@slot.manager.delete_breakpoint(line)
end
##
# Get the help lookup context from the Scintills editor
# It returns the word at or next to cursor plus what's before and
# what's after on the line
#
def help_lookup()
@controller.help_lookup()
end
##
# forward the request for code completion
# to the Ruby Doc plugin via the editpane mgr
#
def code_completion
# Determine where to place the code template
# dialog box on the screen (right below the cursor)
posx = get_ext_object.point_x_from_position(get_ext_object.get_current_pos)
posy = get_ext_object.point_y_from_position(get_ext_object.get_current_pos)
posy += get_ext_object.text_height(get_ext_object.line_from_position(get_ext_object.get_current_pos))
#puts posx,posy
posx,posy = @scintilla.translateCoordinatesTo(plugin["/system/ui/fox/FXMainWindow"].data, posx, posy)
#puts posx,posy
mainx = plugin["/system/ui/fox/FXMainWindow"].data.x
mainy = plugin["/system/ui/fox/FXMainWindow"].data.y
# let the user chose the code template
ctd = CTemplateDialog.new(plugin, mainx+posx, mainy+posy, FreeRIDE::Objects::CODE_TEMPLATES["Ruby"])
ctd.execute(PLACEMENT_VISIBLE)
# insert the code template if any selected
if ctemp = ctd.code_template
# expand selected template
selection = get_ext_object.get_sel_text.chop #chop the trailing \0
if selection.empty?
line_cursor = get_ext_object.line_from_position(get_ext_object.get_current_pos)
line_indent = get_ext_object.get_line_indentation(line_cursor)
insert_pos = get_ext_object.position_from_line(line_cursor)
else
line_selection_start = get_ext_object.line_from_position(get_ext_object.get_selection_start)
line_indent = get_ext_object.get_line_indentation(line_selection_start)
insert_pos = get_ext_object.position_from_line(line_selection_start)
end
indent = get_ext_object.get_indent()
text, cursor_offset, selection_used = ctemp.expand(selection,Hash.new, line_indent, indent)
#insert it
if selection_used
get_ext_object.cut
end
get_ext_object.insert_text(insert_pos, text)
get_ext_object.set_save_point
#place the cursor where it is supposed to be
get_ext_object.goto_pos(insert_pos+cursor_offset)
end
end
end # class Renderer
class CTemplateDialog < FXDialogBox
include Fox
include Responder
MAX_VISIBLE = 15
ID_CTEMP_LIST,
ID_LAST = enum(FXDialogBox::ID_LAST, 2)
def initialize(plugin, x, y, templates)
@plugin = plugin
@templates = templates
owner = plugin["/system/ui/fox/FXMainWindow"].data
@name = ""
FXMAPFUNC(SEL_COMMAND, ID_CTEMP_LIST, :onCmdCTempChosen)
FXMAPFUNC(SEL_KEYPRESS, ID_CTEMP_LIST, :onKeyPress)
FXMAPFUNC(SEL_FOCUSOUT, 0,:onCmdCancel)
FXMAPFUNC(SEL_CLICKED, ID_CTEMP_LIST, :onClicked)
super(owner,"Code Templates", DECOR_STRETCHABLE|DECOR_SHRINKABLE,0,0,0,0,0,0,0,0)
@vfrm = FXVerticalFrame.new(self, LAYOUT_FILL_X|LAYOUT_FILL_Y|FRAME_NONE,0,0,0,0,0,0,0,0,0,0)
vsfrm1 = FXVerticalFrame.new(@vfrm, LAYOUT_FILL_X|LAYOUT_FILL_Y|FRAME_LINE,0,0,0,0,0,0,0,0,0,0)
@list = FXList.new(vsfrm1, self, ID_CTEMP_LIST, LIST_SINGLESELECT|LAYOUT_FILL_X|LAYOUT_FILL_Y|HSCROLLING_OFF)
@list.font = FXFont.new(plugin["/system/ui/fox/FXApp"].data, "courier", 10, FONTWEIGHT_NORMAL, FONTSLANT_REGULAR, FONTENCODING_DEFAULT, FONTSETWIDTH_DONTCARE, FONTPITCH_FIXED)
@list.backColor = FXColor::FloralWhite
vsfrm2 = FXVerticalFrame.new(@vfrm, LAYOUT_FILL_X|LAYOUT_FILL_Y|FRAME_LINE,0,0,0,0,0,0,0,0,0,0)
@tfrm = FXText.new(vsfrm2, nil, 0, TEXT_READONLY|LAYOUT_FILL_X|LAYOUT_FILL_Y|VSCROLLING_OFF|HSCROLLING_OFF)
@tfrm.font = FXFont.new(plugin["/system/ui/fox/FXApp"].data, "courier", 10, FONTWEIGHT_NORMAL, FONTSLANT_REGULAR, FONTENCODING_DEFAULT, FONTSETWIDTH_DONTCARE, FONTPITCH_FIXED)
@tfrm.backColor = FXColor::AliceBlue
self.create
# Fill out the list
build_list()
self.move(x,y)
# make sure the cursor is inside the dialog box to avoid
# spurious FOCUS OUT when resisizing the dialog box
self.setCursorPosition(x+40,y+2)
end
def code_template
@name.empty? ? nil : @templates[@name]
end
def build_list(pattern="")
cts = []
@templates.each_pair do |name, ctemp|
matching = (pattern.empty? || name =~ /^#{pattern}/)
cts << name if matching
end
unless cts.empty?
@list.clearItems
cts.sort.each do |name|
string = sprintf("%-10s%s",name,@templates[name].description)
item = FXListItem.new(string)
idx = @list.appendItem(string)
@list.setItemData(idx, name)
end
@list.numVisible = [@list.numItems, MAX_VISIBLE].min
#@list.numVisible = cts.size
@list.selectItem(0)
@list.currentItem = 0
@tfrm.text = @templates[@list.getItemData(0)].expand()[0]
@list.setFocus
self.resize(self.getDefaultWidth(),self.getDefaultHeight())
end
return !cts.empty?
end
def onKeyPress(sender, sel, event)
rebuild = false
if (event.code >= KEY_A && event.code <= KEY_Z) ||
(event.code >= KEY_a && event.code <= KEY_z) ||
(event.code >= KEY_0 && event.code <= KEY_9)
#puts "#{event.code}"
@name << event.code.chr
@name.chop! unless build_list(@name)
elsif event.code == KEY_BackSpace
@name.chop!
build_list(@name)
elsif event.code == KEY_Escape
@name = ""
onCmdCancel(self,nil,nil)
elsif event.code == KEY_Return || event.code == KEY_KP_Enter
onCmdCTempChosen(self,nil,nil)
elsif event.code == KEY_Down
if @list.currentItem == @list.numItems-1
@list.currentItem = 0
@list.selectItem(0)
else
nxt = @list.currentItem.succ
@list.currentItem = nxt
@list.selectItem(nxt)
end
elsif event.code == KEY_Up
if @list.currentItem == 0
last = @list.numItems-1
@list.currentItem = last
@list.selectItem(last)
else
prev = @list.currentItem - 1
@list.currentItem = prev
@list.selectItem(prev)
end
end
# update template text view
@tfrm.text = text = @templates[@list.getItemData(@list.currentItem)].expand()[0]
@list.numVisible = [@list.numItems, MAX_VISIBLE].min
@list.makeItemVisible(@list.currentItem)
#@list.numVisible = @list.numItems
#@tfrm.visibleRows = 10
self.resize(self.getDefaultWidth(),self.getDefaultHeight())
return 1
end
def onCmdCancel(sender, sel, ptr)
self.handle(self,MKUINT(FXDialogBox::ID_CANCEL,SEL_COMMAND),nil)
return 1
end
def onCmdCTempChosen(sender, sel, ptr)
@name = @list.getItemData(@list.currentItem)
#puts "-- #{@name}"
self.handle(self,MKUINT(FXDialogBox::ID_ACCEPT,SEL_COMMAND),nil)
return 1
end
def onClicked(sender,sel,ptr)
# if the cursor is not on an list item then give up
onCmdCTempChosen(sender, sel, ptr)
end
end
class GotoLineDialog < FXDialogBox
include Fox
def initialize(plugin)
@plugin = plugin
owner = plugin["/system/ui/fox/FXMainWindow"].data
# Invoke base class initialize function first
super(owner, "Goto Line", DECOR_TITLE|DECOR_BORDER|DECOR_CLOSE)
h_frm = FXHorizontalFrame.new(self, LAYOUT_FILL_X)
FXLabel.new(h_frm, "Line: ", nil, JUSTIFY_LEFT|LAYOUT_CENTER_Y)
line_tf = FXTextField.new(h_frm, 12, nil, 0, (FRAME_SUNKEN|
LAYOUT_FILL_X|LAYOUT_CENTER_Y|LAYOUT_FILL_COLUMN))
line_tf.connect(SEL_COMMAND, method(:onCmdFetchLine))
line_tf.setFocus
self.connect(SEL_CLOSE) { self.destroy }
self.create
self.show(PLACEMENT_OWNER)
end
def onCmdFetchLine(sender, sel, ptr)
line = sender.text.to_i
ep_slot = @plugin['/system/ui/current/EditPane']
ep_slot['actions/set_cursor_line'].invoke(line) if line > 0
# return focus to the edit pane when the dialog box closes
ep_slot['actions/make_current'].invoke()
self.destroy
end
end # class GotoLineDialog
class ReplaceDialog < FXDialogBox
include Fox
include Responder
attr_accessor :accept, :every, :inselection, :replacelabel, :replacebox, :searchlast, :searchnext
HORZ_PAD = 10
VERT_PAD = 3
ID_REPLACE,
ID_NEXT_MATCH,
ID_PREV_MATCH,
ID_SEARCH_TEXT,
ID_REPLACE_TEXT,
ID_REPLACE_ALL,
ID_REPLACE_INSEL,
ID_MODE_WHOLEWORD,
ID_MODE_WRAP,
ID_MODE_MATCHCASE,
ID_MODE_BACKSLASH,
ID_MODE_WORDSTART,
ID_MODE_REGEXP,
ID_MODE_DIR,
ID_LAST = enum(FXDialogBox::ID_LAST, 32)
# Search mode flags
SEARCH_WHOLEWORD = 0x1
SEARCH_WRAP = 0x2
SEARCH_MATCHCASE = 0x4
SEARCH_BACKSLASH = 0x8
SEARCH_REGEXP = 0x10
SEARCH_REGEXPPOSIX = 0x20
SEARCH_BACKWARD = 0x40
SEARCH_WORDSTART = 0x80
# Max number of elements in sear/replace combobox history
HISTORY_SIZE = 10
def initialize(plugin)
@plugin = plugin
@app = plugin["/system/ui/fox/FXApp"].data
owner = plugin["/system/ui/fox/FXMainWindow"].data
ic = nil
@havefound = false
FXMAPFUNC(SEL_COMMAND, ID_REPLACE, :onCmdReplaceOnce)
FXMAPFUNC(SEL_COMMAND, ID_REPLACE_TEXT, :onCmdSearchText)
FXMAPFUNC(SEL_COMMAND, ID_SEARCH_TEXT, :onCmdSearchText)
FXMAPFUNC(SEL_COMMAND, ID_REPLACE_ALL, :onCmdReplaceAll)
FXMAPFUNC(SEL_COMMAND, ID_REPLACE_INSEL, :onCmdReplaceAll)
FXMAPFUNC(SEL_COMMAND, ID_PREV_MATCH, :onCmdPrev)
FXMAPFUNC(SEL_COMMAND, ID_NEXT_MATCH, :onCmdNext)
FXMAPFUNC(SEL_COMMAND, ID_MODE_MATCHCASE, :onCmdMatchCase)
FXMAPFUNC(SEL_COMMAND, ID_MODE_WHOLEWORD, :onCmdWholeWord)
FXMAPFUNC(SEL_COMMAND, ID_MODE_WRAP, :onCmdWrap)
FXMAPFUNC(SEL_COMMAND, ID_MODE_REGEXP, :onCmdRegexp)
FXMAPFUNC(SEL_COMMAND, ID_MODE_WORDSTART, :onCmdWordStart)
FXMAPFUNC(SEL_COMMAND, ID_CANCEL, :onCmdCancel)
# Invoke base class initialize function first
super(owner, getDialogTitle, DECOR_TITLE|DECOR_BORDER|DECOR_CLOSE)
buttons = FXVerticalFrame.new(self, LAYOUT_SIDE_RIGHT|LAYOUT_TOP|PACK_UNIFORM_WIDTH)
@searchnext = FXButton.new(buttons,"&Find Next",nil,self,ID_NEXT_MATCH,BUTTON_INITIAL|BUTTON_DEFAULT|FRAME_RAISED|FRAME_THICK|LAYOUT_FILL_Y|LAYOUT_RIGHT,
0,0,0,0,HORZ_PAD,HORZ_PAD,VERT_PAD,VERT_PAD)
@searchlast = FXButton.new(buttons,"Find &Previous",nil,self,ID_PREV_MATCH,BUTTON_INITIAL|BUTTON_DEFAULT|FRAME_RAISED|FRAME_THICK|LAYOUT_FILL_Y|LAYOUT_RIGHT,
0,0,0,0,HORZ_PAD,HORZ_PAD,VERT_PAD,VERT_PAD)
@accept = FXButton.new(buttons,"&Replace",nil,self,ID_REPLACE,BUTTON_INITIAL|BUTTON_DEFAULT|FRAME_RAISED|FRAME_THICK|LAYOUT_FILL_Y|LAYOUT_RIGHT,
0,0,0,0,HORZ_PAD,HORZ_PAD,VERT_PAD,VERT_PAD)
@every = FXButton.new(buttons,"Replace &All",nil,self,ID_REPLACE_ALL,BUTTON_DEFAULT|FRAME_RAISED|FRAME_THICK|LAYOUT_CENTER_Y|LAYOUT_RIGHT,
0,0,0,0,HORZ_PAD,HORZ_PAD,VERT_PAD,VERT_PAD)
@inselection = FXButton.new(buttons,"In &Selection",nil,self,ID_REPLACE_INSEL,BUTTON_DEFAULT|FRAME_RAISED|FRAME_THICK|LAYOUT_CENTER_Y|LAYOUT_RIGHT,
0,0,0,0,HORZ_PAD,HORZ_PAD,VERT_PAD,VERT_PAD)
cancel = FXButton.new(buttons,"&Cancel",nil,self,ID_CANCEL,BUTTON_DEFAULT|FRAME_RAISED|FRAME_THICK|LAYOUT_FILL_Y|LAYOUT_RIGHT,
0,0,0,0,HORZ_PAD,HORZ_PAD,VERT_PAD,VERT_PAD)
entry = FXVerticalFrame.new(self,LAYOUT_SIDE_LEFT|LAYOUT_TOP|LAYOUT_FILL_X, 0,0,0,0, 0,0,0,0)
searchlabel = FXLabel.new(entry,"S&earch for:",nil,JUSTIFY_LEFT|ICON_BEFORE_TEXT|LAYOUT_TOP|LAYOUT_LEFT|LAYOUT_FILL_X)
@searchbox = FXComboBox.new(entry,5,self,ID_SEARCH_TEXT,FRAME_SUNKEN|FRAME_THICK|LAYOUT_SIDE_TOP|LAYOUT_FILL_X)
@searchbox.setNumVisible(5)
@replacelabel = FXLabel.new(entry,"Replace &with:",nil,LAYOUT_LEFT)
@replacebox = FXComboBox.new(entry,5,self,ID_REPLACE_TEXT,FRAME_SUNKEN|FRAME_THICK|LAYOUT_SIDE_TOP|LAYOUT_FILL_X)
@replacebox.setNumVisible(5)
options1 = FXMatrix.new(entry,2, FRAME_NONE|MATRIX_BY_COLUMNS|PACK_UNIFORM_WIDTH|LAYOUT_FILL_Y,0,0,0,0,0,0,0,0,0,0)
@wholeWordChkBtn = FXCheckButton.new(options1,"Match whole word &only",self,ID_MODE_WHOLEWORD,ICON_BEFORE_TEXT|JUSTIFY_LEFT)
@wrapChkBtn = FXCheckButton.new(options1,"Wrap aro&und",self,ID_MODE_WRAP,ICON_BEFORE_TEXT|JUSTIFY_LEFT)
@matchCaseChkBtn = FXCheckButton.new(options1,"&Match case",self,ID_MODE_MATCHCASE,ICON_BEFORE_TEXT|JUSTIFY_LEFT)
@regExpChkBtn = FXCheckButton.new(options1,"Regular e&xpression",self,ID_MODE_REGEXP,ICON_BEFORE_TEXT|JUSTIFY_LEFT)
@matchWordStartChkBtn = FXCheckButton.new(options1,"Matc&h at word start",self,ID_MODE_WORDSTART,ICON_BEFORE_TEXT|JUSTIFY_LEFT)
# Add hot keys
@searchnext.addHotKey(MKUINT(KEY_f,CONTROLMASK))
@searchlast.addHotKey(MKUINT(KEY_p,CONTROLMASK))
searchlast.addHotKey(MKUINT(KEY_b,CONTROLMASK))
@accept.addHotKey(MKUINT(KEY_r,CONTROLMASK))
@every.addHotKey(MKUINT(KEY_a,CONTROLMASK))
@inselection.addHotKey(MKUINT(KEY_s,CONTROLMASK))
cancel.addHotKey(MKUINT(KEY_c,CONTROLMASK))
@searchbox.addHotKey(MKUINT(KEY_e,CONTROLMASK))
@replacebox.addHotKey(MKUINT(KEY_w,CONTROLMASK))
@wholeWordChkBtn.addHotKey(MKUINT(KEY_o,CONTROLMASK))
@wrapChkBtn.addHotKey(MKUINT(KEY_u,CONTROLMASK))
@matchCaseChkBtn.addHotKey(MKUINT(KEY_m,CONTROLMASK))
@regExpChkBtn.addHotKey(MKUINT(KEY_x,CONTROLMASK))
@matchWordStartChkBtn.addHotKey(MKUINT(KEY_h,CONTROLMASK))
# restore search and replace text field state and history
# as well as last search mode
restoreSettings()
end
def getDialogTitle
"Replace Text..."
end
def execute()
self.create
#@searchbox.setFocus
# Mimic a TAB key pressed because the setFocus doesn't work
# on FXComboBox (bug!)
fxevt = FXEvent.new()
fxevt.code = Fox::KEY_Tab
fxevt.type = Fox::SEL_KEYPRESS
@searchbox.handle(self,MKUINT(0, SEL_KEYPRESS),fxevt)
x = @plugin.properties["findsearch/location/x"]
y = @plugin.properties["findsearch/location/y"]
x ||= 1
y ||= 1
position(x,y,self.width,self.height)
if x==1
show(PLACEMENT_OWNER)
else
show
end
@app.runModalFor(self)
end
def model()
ep_slot = @plugin['/system/ui/current/EditPane']
ep_slot['actions/get_ext_object'].invoke()
end
##
# Prompt a message in the status bar
#
def status(msg)
@plugin['/system/ui/current/StatusBar/actions/prompt'].invoke(msg)
end
def replaceText()
@replacebox.getText()
end
def replaceText=(text)
@replacebox.setText(text)
end
def searchText()
@searchbox.getText()
end
def searchText=(text)
@searchbox.setText(text)
end
def searchMode()
@searchmode
end
def searchMode=(mode)
@searchmode = mode
end
def searchBackward? ()
(self.searchMode&SEARCH_BACKWARD) != 0
end
def searchWrap? ()
(self.searchMode&SEARCH_WRAP) != 0
end
def searchWholeWord? ()
(self.searchMode&SEARCH_WHOLEWORD) != 0
end
def searchMatchCase? ()
(self.searchMode&SEARCH_MATCHCASE) != 0
end
def searchRegExp? ()
(self.searchMode&SEARCH_REGEXP) != 0
end
def searchRegExpPosix? ()
(self.searchMode&SEARCH_REGEXPPOSIX) != 0
end
def searchMatchWordStart? ()
(self.searchMode&SEARCH_WORDSTART) != 0
end
def onCmdReplaceOnce(sender, sel, ptr)
if (@havefound)
selstart = model.selection_start
selend = model.selection_end
model.target_start = selstart
model.target_end = selend
if (self.searchRegExp?)
lenreplaced = model.replace_target_re(self.replaceText())
else
lenreplaced = model.replace_target(self.replaceText())
end
model.set_sel(selstart+lenreplaced, selstart)
@havefound = false
end
onCmdNext(sender, sel, ptr)
end
def onCmdReplaceAll(sender, sel, ptr)
if searchText().empty?
return 1
end
findlen = searchText().size
replacelen = replaceText().size
selstart = startpos = model.selection_start
selend = endpos = model.selection_end
if (FXSELID(sel) == ID_REPLACE_INSEL)
return 1 if selstart == selend
else
endpos = model.get_length
# if wrap mode then replace from beginning to end
# else from caret to end
if (self.searchWrap?)
startpos = 0
end
end
searchflags = (searchWholeWord? ? Scintilla::SCFIND_WHOLEWORD : 0) |
(searchMatchCase? ? Scintilla::SCFIND_MATCHCASE : 0) |
(searchRegExp? ? Scintilla::SCFIND_REGEXP : 0) |
(searchMatchWordStart? ? Scintilla::SCFIND_WORDSTART : 0)
model.target_start = startpos
model.target_end = endpos
model.search_flags = searchflags
posfind = model.search_in_target(searchText())
if ((findlen == 1) && self.searchRegExp? && (self.searchText()[0..0] == '^'))
# Special case for replace all start of line so it hits the first line
posfind = startpos;
model.target_start = startpos
model.target_end = startpos
end
if ((posfind != -1) && (posfind <= endpos))
lastmatch = posfind
occurences = 0
model.begin_undo_action()
while (posfind != -1)
lentarget = model.target_end - model.target_start
movepasteol = 0
if (lentarget <= 0)
nextchar = model.char_at(model.target_end)
if (nextchar=="\r" || nextchar=="\n")
# FIXME? shall we test EOL mode and add 2 if it is
# SC_EOL_CRLF
movepasteol = 1
end
end
lenreplaced = replacelen
if (self.searchRegExp?)
lenreplaced = model.replace_target_re(self.replaceText())
else
lenreplaced = model.replace_target(self.replaceText())
end
# modify end position of the target to reflect the last change
endpos += lenreplaced - lentarget
lastmatch = posfind + lenreplaced + movepasteol
if (lastmatch >= endpos)
# run off the endof the document with an empty match
posfind = -1
else
model.target_start = lastmatch
model.target_end = endpos
posfind = model.search_in_target(searchText())
end
occurences = occurences.succ
end #while
if (FXSELID(sel) == ID_REPLACE_INSEL)
model.set_sel(startpos,endpos)
else
model.set_sel(lastmatch,lastmatch)
end
model.end_undo_action()
status("Replaced #{occurences} occurence"+(occurences>1 ? 's':''))
end # if
return 1
end
def onCmdPrev(sender, sel, ptr)
self.searchMode |= SEARCH_BACKWARD
onCmdNext(sender, sel, ptr)
end
def onCmdNext(sender, sel, ptr)
@havefound = false
if searchText().empty?
self.searchMode &= ~SEARCH_BACKWARD
return 1
end
selstart = model.selection_start
selend = model.selection_end
if (searchBackward?)
startpos = selstart - 1;
endpos = 0;
else
startpos = selend
endpos = model.length
end
searchflags = (searchWholeWord? ? Scintilla::SCFIND_WHOLEWORD : 0) |
(searchMatchCase? ? Scintilla::SCFIND_MATCHCASE : 0) |
(searchRegExp? ? Scintilla::SCFIND_REGEXP : 0) |
(searchMatchWordStart? ? Scintilla::SCFIND_WORDSTART : 0)
model.target_start = startpos
model.target_end = endpos
model.search_flags = searchflags
posfind = model.search_in_target(searchText())
if (posfind == -1 && searchWrap?)
# Failed to find in indicated direction
# so search from the beginning (forward) or from the end (reverse)
if ( searchBackward? )
startpos = model.length
endpos = 0
else
startpos = 0
endpos = model.length
end
model.target_start = startpos
model.target_end = endpos
posfind = model.search_in_target(searchText())
status("Failing search: #{searchText()}. About to wrap")
end
if (posfind == -1)
status("Failing search: #{searchText()}")
else
@havefound = true
start = model.target_start()
finish = model.target_end()
model.set_sel(start,finish)
status("Found '#{searchText()}' at line #{model.line_from_position(start)}, column #{model.get_column(start)}")
end
self.searchMode &= ~SEARCH_BACKWARD
@app.stopModal(self)
return 1
end
def restoreSettings()
@search_history = @plugin.properties['findreplace/search_hist'] || Array.new
@replace_history = @plugin.properties['findreplace/replace_hist'] || Array.new
@search_history.slice!(-HISTORY_SIZE..-1) if @search_history.size > HISTORY_SIZE
@replace_history.slice!(-HISTORY_SIZE..-1) if @replace_history.size > HISTORY_SIZE
@last_search = @plugin.properties['findreplace/last_search']
@last_replace = @plugin.properties['findreplace/last_replace']
@searchmode = @plugin.properties['findreplace/search_mode'] || SEARCH_WRAP
@search_history.each { |elt| @searchbox.prependItem(elt) }
@replace_history.each { |elt| @replacebox.prependItem(elt) }
@wholeWordChkBtn.checkState = get_check_state(searchWholeWord?)
@wrapChkBtn.checkState = get_check_state(searchWrap?)
@matchCaseChkBtn.checkState = get_check_state(searchMatchCase?)
@regExpChkBtn.checkState = get_check_state(searchRegExp?)
@matchWordStartChkBtn.checkState = get_check_state(searchMatchWordStart?)
unless @search_history.empty?
self.searchText = @last_search
end
unless @replace_history.empty?
self.replaceText = @last_replace
end
end
def get_check_state(bool)
bool ? TRUE : FALSE
end
def saveSettings()
@plugin.properties.auto_save = false
@plugin.properties["findsearch/location/x"] = self.x
@plugin.properties["findsearch/location/y"] = self.y
@plugin.properties['findreplace/search_hist'] = @search_history
@plugin.properties['findreplace/last_search'] = @last_search
@plugin.properties['findreplace/replace_hist'] = @replace_history
@plugin.properties['findreplace/last_replace'] = @last_replace
@plugin.properties['findreplace/search_mode'] = @searchmode
@plugin.properties.auto_save = true
@plugin.properties.save
end
def appendHistory(search_text, replace_text)
@last_search = elt = searchText()
unless @search_history.include?(elt)
@search_history << elt
@search_history.shift if @search_history.size > HISTORY_SIZE
end
@last_replace = elt = replaceText()
unless @replace_history.include?(elt)
@replace_history << elt
@replace_history.shift if @replace_history.size > HISTORY_SIZE
end
end
def onCmdSearchText(sender, sel, ptr)
return 0 if sender.text.empty?
# let's keep our own history
appendHistory(searchText(), replaceText())
# only insert the new element in the combobox
# list if it doesn't yet exist
found = false
text = sender.text
num = sender.getNumItems
for idx in 0..num-1
if sender.getItemText(idx) == text
found = true
sender.setCurrentItem(idx)
break
end
end
# insert the new item if needed and only keep
# the last HISTORY_SIZE elements
unless found
sender.prependItem(text)
sender.setCurrentItem(0)
sender.removeItem(num) if num == HISTORY_SIZE
end
# This mehtod is called whenever the return key is typed
# so also search for the next/prev occurence
(self.searchMode & SEARCH_BACKWARD) != 0 ? onCmdPrev(sender,sel,ptr) : onCmdNext(sender,sel,ptr)
return 1
end
def onCmdMatchCase(sender, sel, ptr)
self.searchMode ^= SEARCH_MATCHCASE
return 1
end
def onCmdWholeWord(sender, sel, ptr)
self.searchMode ^= SEARCH_WHOLEWORD
return 1
end
def onCmdWrap(sender, sel, ptr)
self.searchMode ^= SEARCH_WRAP
return 1
end
def onCmdRegexp(sender, sel, ptr)
self.searchMode ^= SEARCH_REGEXP
return 1
end
def onCmdWordStart(sender, sel, ptr)
self.searchMode ^= SEARCH_WORDSTART
return 1
end
def onCmdCancel(sender, sel, ptr)
saveSettings
@app.stopModal(self)
self.destroy
return 1
end
end # class FindDialog
##
# The Search Dialog box is basically the same as the replace
# dialog box minus some hidden fields
#
class FindDialog < ReplaceDialog
def initialize(plugin)
super(plugin)
# hide all controls that are for text replacement
accept.hide
every.hide
replacelabel.hide
replacebox.hide
inselection.hide
accept.disable
every.disable
inselection.disable
# if there is a piece of text selected in the current edit pane
# then this is the text to search
ep_slot = @plugin['/system/ui/current/EditPane']
model = ep_slot['actions/get_ext_object'].invoke()
text_selection = model.get_sel_text()
unless text_selection.empty?
self.searchText = text_selection
end
end
def getDialogTitle
"Find Text..."
end
end # class FindDialog
end # class EditPane
end
end
syntax highlighted by Code2HTML, v. 0.9.1