PageRenderTime 149ms CodeModel.GetById 40ms app.highlight 63ms RepoModel.GetById 37ms app.codeStats 1ms

/jEdit/tags/jedit-4-3-pre5/org/gjt/sp/jedit/Registers.java

#
Java | 739 lines | 547 code | 47 blank | 145 comment | 66 complexity | 4c42c64a8401c70982936c81f808467b MD5 | raw file
  1/*
  2 * Registers.java - Register manager
  3 * :tabSize=8:indentSize=8:noTabs=false:
  4 * :folding=explicit:collapseFolds=1:
  5 *
  6 * Copyright (C) 1999, 2003 Slava Pestov
  7 *
  8 * This program is free software; you can redistribute it and/or
  9 * modify it under the terms of the GNU General Public License
 10 * as published by the Free Software Foundation; either version 2
 11 * of the License, or any later version.
 12 *
 13 * This program is distributed in the hope that it will be useful,
 14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 16 * GNU General Public License for more details.
 17 *
 18 * You should have received a copy of the GNU General Public License
 19 * along with this program; if not, write to the Free Software
 20 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 21 */
 22
 23package org.gjt.sp.jedit;
 24
 25//{{{ Imports
 26import java.awt.datatransfer.*;
 27import java.awt.Toolkit;
 28import java.io.*;
 29
 30import org.xml.sax.Attributes;
 31import org.xml.sax.InputSource;
 32import org.xml.sax.helpers.DefaultHandler;
 33
 34import org.gjt.sp.jedit.buffer.JEditBuffer;
 35import org.gjt.sp.jedit.gui.*;
 36import org.gjt.sp.jedit.msg.RegisterChanged;
 37import org.gjt.sp.jedit.textarea.*;
 38import org.gjt.sp.util.Log;
 39//}}}
 40
 41/**
 42 * jEdit's registers are an extension of the clipboard metaphor.<p>
 43 *
 44 * A {@link Registers.Register} is string of text indexed by a
 45 * single character. Typically the text is taken from selected buffer text
 46 * and the index character is a keyboard character selected by the user.<p>
 47 *
 48 * This class defines a number of static methods
 49 * that give each register the properties of a virtual clipboard.<p>
 50 *
 51 * Two classes implement the {@link Registers.Register} interface. A
 52 * {@link Registers.ClipboardRegister} is tied to the contents of the
 53 * system clipboard. jEdit assigns a
 54 * {@link Registers.ClipboardRegister} to the register indexed under
 55 * the character <code>$</code>. A
 56 * {@link Registers.StringRegister} is created for registers assigned
 57 * by the user. In addition, jEdit assigns <code>%</code> to
 58 * the last text segment selected in the text area. On Windows this is a
 59 * {@link Registers.StringRegister}, on Unix under Java 2 version 1.4, a
 60 * {@link Registers.ClipboardRegister}.
 61 *
 62 * @author Slava Pestov
 63 * @author John Gellene (API documentation)
 64 * @version $Id: Registers.java 5443 2006-06-18 18:51:40Z vanza $
 65 */
 66public class Registers
 67{
 68	//{{{ copy() method
 69	/**
 70	 * Copies the text selected in the text area into the specified register.
 71	 * This will replace the existing contents of the designated register.
 72	 *
 73	 * @param textArea The text area
 74	 * @param register The register
 75	 * @since jEdit 2.7pre2
 76	 */
 77	public static void copy(JEditTextArea textArea, char register)
 78	{
 79		String selection = textArea.getSelectedText();
 80		if(selection == null)
 81			return;
 82
 83		setRegister(register,selection);
 84		HistoryModel.getModel("clipboard").addItem(selection);
 85
 86	} //}}}
 87
 88	//{{{ cut() method
 89	/**
 90	 * Copies the text selected in the text area into the specified
 91	 * register, and then removes it from the buffer.
 92	 *
 93	 * @param textArea The text area
 94	 * @param register The register
 95	 * @since jEdit 2.7pre2
 96	 */
 97	public static void cut(JEditTextArea textArea, char register)
 98	{
 99		if(textArea.isEditable())
100		{
101			String selection = textArea.getSelectedText();
102			if(selection == null)
103				return;
104
105			setRegister(register,selection);
106			HistoryModel.getModel("clipboard").addItem(selection);
107
108			textArea.setSelectedText("");
109		}
110		else
111			textArea.getToolkit().beep();
112	} //}}}
113
114	//{{{ append() method
115	/**
116	 * Appends the text selected in the text area to the specified register,
117	 * with a newline between the old and new text.
118	 * @param textArea The text area
119	 * @param register The register
120	 */
121	public static void append(JEditTextArea textArea, char register)
122	{
123		append(textArea,register,"\n",false);
124	} //}}}
125
126	//{{{ append() method
127	/**
128	 * Appends the text selected in the text area to the specified register.
129	 * @param textArea The text area
130	 * @param register The register
131	 * @param separator The separator to insert between the old and new text
132	 */
133	public static void append(JEditTextArea textArea, char register,
134		String separator)
135	{
136		append(textArea,register,separator,false);
137	} //}}}
138
139	//{{{ append() method
140	/**
141	 * Appends the text selected in the  text area to the specified register.
142	 * @param textArea The text area
143	 * @param register The register
144	 * @param separator The text to insert between the old and new text
145	 * @param cut Should the current selection be removed?
146	 * @since jEdit 3.2pre1
147	 */
148	public static void append(JEditTextArea textArea, char register,
149		String separator, boolean cut)
150	{
151		if(cut && !textArea.isEditable())
152		{
153			textArea.getToolkit().beep();
154			return;
155		}
156
157		String selection = textArea.getSelectedText();
158		if(selection == null)
159			return;
160
161		Register reg = getRegister(register);
162
163		if(reg != null)
164		{
165			String registerContents = reg.toString();
166			if(registerContents != null)
167			{
168				if(registerContents.endsWith(separator))
169					selection = registerContents + selection;
170				else
171					selection = registerContents + separator + selection;
172			}
173		}
174
175		setRegister(register,selection);
176		HistoryModel.getModel("clipboard").addItem(selection);
177
178		if(cut)
179			textArea.setSelectedText("");
180	} //}}}
181
182	//{{{ paste() method
183	/**
184	 * Insets the contents of the specified register into the text area.
185	 * @param textArea The text area
186	 * @param register The register
187	 * @since jEdit 2.7pre2
188	 */
189	public static void paste(JEditTextArea textArea, char register)
190	{
191		paste(textArea,register,false);
192	} //}}}
193
194	//{{{ paste() method
195	/**
196	 * Inserts the contents of the specified register into the text area.
197	 * @param textArea The text area
198	 * @param register The register
199	 * @param vertical Vertical (columnar) paste
200	 * @since jEdit 4.1pre1
201	 */
202	public static void paste(JEditTextArea textArea, char register,
203		boolean vertical)
204	{
205		if(!textArea.isEditable())
206		{
207			textArea.getToolkit().beep();
208			return;
209		}
210
211		Register reg = getRegister(register);
212
213		if(reg == null)
214		{
215			textArea.getToolkit().beep();
216			return;
217		}
218		else
219		{
220			String selection = reg.toString();
221			if(selection == null)
222			{
223				textArea.getToolkit().beep();
224				return;
225			}
226			JEditBuffer buffer = textArea.getBuffer();
227			try
228			{
229				buffer.beginCompoundEdit();
230
231				/* vertical paste */
232				if(vertical && textArea.getSelectionCount() == 0)
233				{
234					int caret = textArea.getCaretPosition();
235					int caretLine = textArea.getCaretLine();
236					Selection.Rect rect = new Selection.Rect(
237						caretLine,caret,caretLine,caret);
238					textArea.setSelectedText(rect,selection);
239					caretLine = textArea.getCaretLine();
240
241					if(caretLine != textArea.getLineCount() - 1)
242					{
243						int startColumn = rect.getStartColumn(
244							buffer);
245						int offset = buffer
246							.getOffsetOfVirtualColumn(
247							caretLine + 1,startColumn,null);
248						if(offset == -1)
249						{
250							buffer.insertAtColumn(caretLine + 1,startColumn,"");
251							textArea.setCaretPosition(
252								buffer.getLineEndOffset(
253								caretLine + 1) - 1);
254						}
255						else
256						{
257							textArea.setCaretPosition(
258								buffer.getLineStartOffset(
259								caretLine + 1) + offset);
260						}
261					}
262				}
263				else /* Regular paste */
264				{
265					textArea.replaceSelection(selection);
266				}
267			}
268			finally {
269				buffer.endCompoundEdit();
270			}
271			HistoryModel.getModel("clipboard").addItem(selection);
272		}
273	} //}}}
274
275	//{{{ getRegister() method
276	/**
277	 * Returns the specified register.
278	 * @param name The name
279	 */
280	public static Register getRegister(char name)
281	{
282		if(name != '$' && name != '%')
283		{
284			if(!loaded)
285				loadRegisters();
286		}
287
288		if(registers == null || name >= registers.length)
289			return null;
290		else
291			return registers[name];
292	} //}}}
293
294	//{{{ setRegister() method
295	/**
296	 * Sets the specified register.
297	 * @param name The name
298	 * @param newRegister The new value
299	 */
300	public static void setRegister(char name, Register newRegister)
301	{
302		touchRegister(name);
303
304		if(name >= registers.length)
305		{
306			Register[] newRegisters = new Register[
307				Math.min(1<<16,name * 2)];
308			System.arraycopy(registers,0,newRegisters,0,
309				registers.length);
310			registers = newRegisters;
311		}
312
313		registers[name] = newRegister;
314		EditBus.send(new RegisterChanged(null,name));
315	} //}}}
316
317	//{{{ setRegister() method
318	/**
319	 * Sets the specified register.
320	 * @param name The name
321	 * @param value The new value
322	 */
323	public static void setRegister(char name, String value)
324	{
325		touchRegister(name);
326		Register register = getRegister(name);
327		if(register != null)
328		{
329			register.setValue(value);
330			EditBus.send(new RegisterChanged(null,name));
331		}
332		else
333			setRegister(name,new StringRegister(value));
334	} //}}}
335
336	//{{{ clearRegister() method
337	/**
338	 * Sets the value of the specified register to <code>null</code>.
339	 * @param name The register name
340	 */
341	public static void clearRegister(char name)
342	{
343		if(name >= registers.length)
344			return;
345
346		Register register = registers[name];
347		if(name == '$' || name == '%')
348			register.setValue("");
349		else
350			registers[name] = null;
351	} //}}}
352
353	//{{{ getRegisters() method
354	/**
355	 * Returns an array of all available registers. Some of the elements
356	 * of this array might be <code>null</code>.
357	 */
358	public static Register[] getRegisters()
359	{
360		if(!loaded)
361			loadRegisters();
362		return registers;
363	} //}}}
364
365	//{{{ getRegisterStatusPrompt() method
366	/**
367	 * Returns the status prompt for the given register action. Only
368	 * intended to be called from <code>actions.xml</code>.
369	 * @since jEdit 4.2pre2
370	 */
371	public static String getRegisterStatusPrompt(String action)
372	{
373		return jEdit.getProperty("view.status." + action,
374			new String[] { getRegisterNameString() });
375	} //}}}
376
377	//{{{ getRegisterNameString() method
378	/**
379	 * Returns a string of all defined registers, used by the status bar
380	 * (eg, "a b $ % ^").
381	 * @since jEdit 4.2pre2
382	 */
383	public static String getRegisterNameString()
384	{
385		if(!loaded)
386			loadRegisters();
387
388		StringBuffer buf = new StringBuffer();
389		for(int i = 0; i < registers.length; i++)
390		{
391			if(registers[i] != null)
392			{
393				if(buf.length() != 0)
394					buf.append(' ');
395				buf.append((char)i);
396			}
397		}
398
399		if(buf.length() == 0)
400			return jEdit.getProperty("view.status.no-registers");
401		else
402			return buf.toString();
403	} //}}}
404
405	//{{{ saveRegisters() method
406	public static void saveRegisters()
407	{
408		if(!loaded || !modified)
409			return;
410
411		Log.log(Log.MESSAGE,Registers.class,"Saving registers.xml");
412		File file1 = new File(MiscUtilities.constructPath(
413			jEdit.getSettingsDirectory(), "#registers.xml#save#"));
414		File file2 = new File(MiscUtilities.constructPath(
415			jEdit.getSettingsDirectory(), "registers.xml"));
416		if(file2.exists() && file2.lastModified() != registersModTime)
417		{
418			Log.log(Log.WARNING,Registers.class,file2 + " changed"
419				+ " on disk; will not save registers");
420			return;
421		}
422
423		jEdit.backupSettingsFile(file2);
424
425		String lineSep = System.getProperty("line.separator");
426
427		BufferedWriter out = null;
428
429		boolean ok = false;
430
431		try
432		{
433			out = new BufferedWriter(new FileWriter(file1));
434
435			out.write("<?xml version=\"1.0\"?>");
436			out.write(lineSep);
437			out.write("<!DOCTYPE REGISTERS SYSTEM \"registers.dtd\">");
438			out.write(lineSep);
439			out.write("<REGISTERS>");
440			out.write(lineSep);
441
442			Register[] registers = getRegisters();
443			for(int i = 0; i < registers.length; i++)
444			{
445				Register register = registers[i];
446				if(register == null ||
447                                   i == '$' ||
448                                   i == '%' ||
449                                   register.toString().length() == 0
450                                  )
451					continue;
452
453				out.write("<REGISTER NAME=\"");
454				if(i == '"')
455					out.write("&quot;");
456				else
457					out.write((char)i);
458				out.write("\">");
459
460				out.write(MiscUtilities.charsToEntities(
461					register.toString()));
462
463				out.write("</REGISTER>");
464				out.write(lineSep);
465			}
466
467			out.write("</REGISTERS>");
468			out.write(lineSep);
469
470			ok = true;
471		}
472		catch(Exception e)
473		{
474			Log.log(Log.ERROR,Registers.class,e);
475		}
476		finally
477		{
478			try
479			{
480				if(out != null)
481					out.close();
482			}
483			catch(IOException e)
484			{
485			}
486		}
487
488		if(ok)
489		{
490			/* to avoid data loss, only do this if the above
491			 * completed successfully */
492			file2.delete();
493			file1.renameTo(file2);
494		}
495
496		registersModTime = file2.lastModified();
497		modified = false;
498	} //}}}
499
500	//{{{ Private members
501	private static Register[] registers;
502	private static long registersModTime;
503	private static boolean loaded, loading, modified;
504
505	private Registers() {}
506
507	static
508	{
509		registers = new Register[256];
510		Toolkit toolkit = Toolkit.getDefaultToolkit();
511		registers['$'] = new ClipboardRegister(
512			toolkit.getSystemClipboard());
513		Clipboard selection = toolkit.getSystemSelection();
514		if(selection != null)
515			registers['%'] = new ClipboardRegister(selection);
516	}
517
518	//{{{ touchRegister() method
519	private static void touchRegister(char name)
520	{
521		if(name == '%' || name == '$')
522			return;
523
524		if(!loaded)
525			loadRegisters();
526
527		if(!loading)
528			modified = true;
529	} //}}}
530
531	//{{{ loadRegisters() method
532	private static void loadRegisters()
533	{
534		loaded = true;
535
536		String settingsDirectory = jEdit.getSettingsDirectory();
537		if(settingsDirectory == null)
538			return;
539
540		File registerFile = new File(MiscUtilities.constructPath(
541			jEdit.getSettingsDirectory(),"registers.xml"));
542		if(!registerFile.exists())
543			return;
544
545		registersModTime = registerFile.lastModified();
546
547		Log.log(Log.MESSAGE,jEdit.class,"Loading registers.xml");
548
549		RegistersHandler handler = new RegistersHandler();
550		try {
551			loading = true;
552			MiscUtilities.parseXML(new FileInputStream(registerFile),
553						handler);
554		}
555		catch (IOException ioe) {
556			Log.log(Log.ERROR, Registers.class, ioe);
557		} finally {
558			loading = false;
559		}
560	} //}}}
561
562	//}}}
563
564	//{{{ Inner classes
565
566	//{{{ Register interface
567	/**
568	 * A register.
569	 */
570	public interface Register
571	{
572		/**
573		 * Converts to a string.
574		 */
575		String toString();
576
577		/**
578		 * Sets the register contents.
579		 */
580		void setValue(String value);
581	} //}}}
582
583	//{{{ ClipboardRegister class
584	/**
585	 * A clipboard register. Register "$" should always be an
586	 * instance of this.
587	 */
588	public static class ClipboardRegister implements Register
589	{
590		Clipboard clipboard;
591
592		public ClipboardRegister(Clipboard clipboard)
593		{
594			this.clipboard = clipboard;
595		}
596
597		/**
598		 * Sets the clipboard contents.
599		 */
600		public void setValue(String value)
601		{
602			StringSelection selection = new StringSelection(value);
603			clipboard.setContents(selection,null);
604		}
605
606		/**
607		 * Returns the clipboard contents.
608		 */
609		public String toString()
610		{
611			try
612			{
613				String selection = (String)(clipboard
614					.getContents(this).getTransferData(
615					DataFlavor.stringFlavor));
616
617				boolean trailingEOL = (selection.endsWith("\n")
618					|| selection.endsWith(System.getProperty(
619					"line.separator")));
620
621				// Some Java versions return the clipboard
622				// contents using the native line separator,
623				// so have to convert it here
624				BufferedReader in = new BufferedReader(
625					new StringReader(selection));
626				StringBuffer buf = new StringBuffer();
627				String line;
628				while((line = in.readLine()) != null)
629				{
630					// broken Eclipse workaround!
631					// 24 Febuary 2004
632					if(line.endsWith("\0"))
633					{
634						line = line.substring(0,
635							line.length() - 1);
636					}
637					buf.append(line);
638					buf.append('\n');
639				}
640				// remove trailing \n
641				if(!trailingEOL && buf.length() != 0)
642					buf.setLength(buf.length() - 1);
643				return buf.toString();
644			}
645			catch(Exception e)
646			{
647				Log.log(Log.NOTICE,this,e);
648				return null;
649			}
650		}
651	} //}}}
652
653	//{{{ StringRegister class
654	/**
655	 * Register that stores a string.
656	 */
657	public static class StringRegister implements Register
658	{
659		private String value;
660
661		/**
662		 * Creates a new string register.
663		 * @param value The contents
664		 */
665		public StringRegister(String value)
666		{
667			this.value = value;
668		}
669
670		/**
671		 * Sets the register contents.
672		 */
673		public void setValue(String value)
674		{
675			this.value = value;
676		}
677
678		/**
679		 * Converts to a string.
680		 */
681		public String toString()
682		{
683			return value;
684		}
685
686		/**
687		 * Called when this register is no longer available. This
688		 * implementation does nothing.
689		 */
690		public void dispose() {}
691	} //}}}
692
693	//{{{ RegistersHandler class
694	static class RegistersHandler extends DefaultHandler
695	{
696		//{{{ resolveEntity() method
697		public InputSource resolveEntity(String publicId, String systemId)
698		{
699			return MiscUtilities.findEntity(systemId, "registers.dtd", getClass());
700		} //}}}
701
702		//{{{ startElement() method
703		public void startElement(String uri, String localName,
704					 String qName, Attributes attrs)
705		{
706			registerName = attrs.getValue("NAME");
707			inRegister = "REGISTER".equals(qName);
708		} //}}}
709
710		//{{{ endElement() method
711		public void endElement(String uri, String localName, String name)
712		{
713			if(name.equals("REGISTER"))
714			{
715				if(registerName == null || registerName.length() != 1)
716					Log.log(Log.ERROR,this,"Malformed NAME: " + registerName);
717				else
718					setRegister(registerName.charAt(0),charData.toString());
719				inRegister = false;
720				charData.setLength(0);
721			}
722		} //}}}
723
724		//{{{ characters() method
725		public void characters(char[] ch, int start, int length)
726		{
727			if (inRegister)
728				charData.append(ch, start, length);
729		} //}}}
730
731		//{{{ Private members
732		private String registerName;
733		private StringBuffer charData = new StringBuffer();
734		private boolean inRegister;
735		//}}}
736	} //}}}
737
738	//}}}
739}