PageRenderTime 222ms CodeModel.GetById 198ms app.highlight 19ms RepoModel.GetById 1ms app.codeStats 0ms

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

#
Java | 741 lines | 551 code | 46 blank | 144 comment | 73 complexity | 1dc120fcb59af0117444f3727c0c79df 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 4835 2003-07-23 06:08:54Z 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		try
424		{
425			BufferedWriter out = new BufferedWriter(
426				new FileWriter(file1));
427
428			out.write("<?xml version=\"1.0\"?>");
429			out.write(lineSep);
430			out.write("<!DOCTYPE REGISTERS SYSTEM \"registers.dtd\">");
431			out.write(lineSep);
432			out.write("<REGISTERS>");
433			out.write(lineSep);
434
435			Register[] registers = getRegisters();
436			for(int i = 0; i < registers.length; i++)
437			{
438				Register register = registers[i];
439				if(register == null || i == '$' || i == '%')
440					continue;
441
442				out.write("<REGISTER NAME=\"");
443				if(i == '"')
444					out.write("&quot;");
445				else
446					out.write((char)i);
447				out.write("\">");
448
449				out.write(MiscUtilities.charsToEntities(
450					register.toString()));
451
452				out.write("</REGISTER>");
453				out.write(lineSep);
454			}
455
456			out.write("</REGISTERS>");
457			out.write(lineSep);
458
459			out.close();
460
461			/* to avoid data loss, only do this if the above
462			 * completed successfully */
463			file2.delete();
464			file1.renameTo(file2);
465		}
466		catch(Exception e)
467		{
468			Log.log(Log.ERROR,Registers.class,e);
469		}
470
471		registersModTime = file2.lastModified();
472		modified = false;
473	} //}}}
474
475	//{{{ Private members
476	private static Register[] registers;
477	private static long registersModTime;
478	private static boolean loaded, loading, modified;
479
480	private Registers() {}
481
482	static
483	{
484		registers = new Register[256];
485		registers['$'] = new ClipboardRegister(Toolkit
486			.getDefaultToolkit().getSystemClipboard());
487	}
488
489	//{{{ loadRegisters() method
490	private static void loadRegisters()
491	{
492		String settingsDirectory = jEdit.getSettingsDirectory();
493		if(settingsDirectory == null)
494			return;
495
496		File registerFile = new File(MiscUtilities.constructPath(
497			jEdit.getSettingsDirectory(),"registers.xml"));
498		if(!registerFile.exists())
499			return;
500
501		registersModTime = registerFile.lastModified();
502		loaded = true;
503
504		Log.log(Log.MESSAGE,jEdit.class,"Loading registers.xml");
505
506		RegistersHandler handler = new RegistersHandler();
507		XmlParser parser = new XmlParser();
508		parser.setHandler(handler);
509		Reader in = null;
510		try
511		{
512			loading = true;
513			in = new BufferedReader(new FileReader(registerFile));
514			parser.parse(null, null, in);
515		}
516		catch(XmlException xe)
517		{
518			int line = xe.getLine();
519			String message = xe.getMessage();
520			Log.log(Log.ERROR,Registers.class,registerFile + ":"
521				+ line + ": " + message);
522		}
523		catch(FileNotFoundException fnf)
524		{
525			//Log.log(Log.DEBUG,Registers.class,fnf);
526		}
527		catch(Exception e)
528		{
529			Log.log(Log.ERROR,Registers.class,e);
530		}
531		finally
532		{
533			loading = false;
534			try
535			{
536				if(in != null)
537					in.close();
538			}
539			catch(IOException io)
540			{
541				Log.log(Log.ERROR,Registers.class,io);
542			}
543		}
544	} //}}}
545
546	//}}}
547
548	//{{{ Inner classes
549
550	//{{{ Register interface
551	/**
552	 * A register.
553	 */
554	public interface Register
555	{
556		/**
557		 * Converts to a string.
558		 */
559		String toString();
560
561		/**
562		 * Sets the register contents.
563		 */
564		void setValue(String value);
565	} //}}}
566
567	//{{{ ClipboardRegister class
568	/**
569	 * A clipboard register. Register "$" should always be an
570	 * instance of this.
571	 */
572	public static class ClipboardRegister implements Register
573	{
574		Clipboard clipboard;
575
576		public ClipboardRegister(Clipboard clipboard)
577		{
578			this.clipboard = clipboard;
579		}
580
581		/**
582		 * Sets the clipboard contents.
583		 */
584		public void setValue(String value)
585		{
586			StringSelection selection = new StringSelection(value);
587			clipboard.setContents(selection,null);
588		}
589
590		/**
591		 * Returns the clipboard contents.
592		 */
593		public String toString()
594		{
595			try
596			{
597				String selection = (String)(clipboard
598					.getContents(this).getTransferData(
599					DataFlavor.stringFlavor));
600
601				boolean trailingEOL = (selection.endsWith("\n")
602					|| selection.endsWith(System.getProperty(
603					"line.separator")));
604
605				// Some Java versions return the clipboard
606				// contents using the native line separator,
607				// so have to convert it here
608				BufferedReader in = new BufferedReader(
609					new StringReader(selection));
610				StringBuffer buf = new StringBuffer();
611				String line;
612				while((line = in.readLine()) != null)
613				{
614					buf.append(line);
615					buf.append('\n');
616				}
617				// remove trailing \n
618				if(!trailingEOL && buf.length() != 0)
619					buf.setLength(buf.length() - 1);
620				return buf.toString();
621			}
622			catch(Exception e)
623			{
624				Log.log(Log.NOTICE,this,e);
625				return null;
626			}
627		}
628	} //}}}
629
630	//{{{ StringRegister class
631	/**
632	 * Register that stores a string.
633	 */
634	public static class StringRegister implements Register
635	{
636		private String value;
637
638		/**
639		 * Creates a new string register.
640		 * @param value The contents
641		 */
642		public StringRegister(String value)
643		{
644			this.value = value;
645		}
646
647		/**
648		 * Sets the register contents.
649		 */
650		public void setValue(String value)
651		{
652			this.value = value;
653		}
654
655		/**
656		 * Converts to a string.
657		 */
658		public String toString()
659		{
660			return value;
661		}
662
663		/**
664		 * Called when this register is no longer available. This
665		 * implementation does nothing.
666		 */
667		public void dispose() {}
668	} //}}}
669
670	//{{{ RegistersHandler class
671	static class RegistersHandler extends HandlerBase
672	{
673		//{{{ resolveEntity() method
674		public Object resolveEntity(String publicId, String systemId)
675		{
676			if("registers.dtd".equals(systemId))
677			{
678				// this will result in a slight speed up, since we
679				// don't need to read the DTD anyway, as AElfred is
680				// non-validating
681				return new StringReader("<!-- -->");
682
683				/* try
684				{
685					return new BufferedReader(new InputStreamReader(
686						getClass().getResourceAsStream("registers.dtd")));
687				}
688				catch(Exception e)
689				{
690					Log.log(Log.ERROR,this,"Error while opening"
691						+ " recent.dtd:");
692					Log.log(Log.ERROR,this,e);
693				} */
694			}
695
696			return null;
697		} //}}}
698
699		//{{{ attribute() method
700		public void attribute(String aname, String value, boolean isSpecified)
701		{
702			if(aname.equals("NAME"))
703				registerName = value;
704		} //}}}
705
706		//{{{ doctypeDecl() method
707		public void doctypeDecl(String name, String publicId,
708			String systemId) throws Exception
709		{
710			if("REGISTERS".equals(name))
711				return;
712
713			Log.log(Log.ERROR,this,"registers.xml: DOCTYPE must be REGISTERS");
714		} //}}}
715
716		//{{{ endElement() method
717		public void endElement(String name)
718		{
719			if(name.equals("REGISTER"))
720			{
721				if(registerName == null || registerName.length() != 1)
722					Log.log(Log.ERROR,this,"Malformed NAME: " + registerName);
723				else
724					setRegister(registerName.charAt(0),charData);
725			}
726		} //}}}
727
728		//{{{ charData() method
729		public void charData(char[] ch, int start, int length)
730		{
731			charData = new String(ch,start,length);
732		} //}}}
733
734		//{{{ Private members
735		private String registerName;
736		private String charData;
737		//}}}
738	} //}}}
739
740	//}}}
741}