PageRenderTime 79ms CodeModel.GetById 37ms app.highlight 37ms RepoModel.GetById 1ms app.codeStats 0ms

/jEdit/tags/jedit-4-2-pre14/org/gjt/sp/jedit/Registers.java

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