001: /*
002: * Registers.java - Register manager
003: * :tabSize=8:indentSize=8:noTabs=false:
004: * :folding=explicit:collapseFolds=1:
005: *
006: * Copyright (C) 1999, 2003 Slava Pestov
007: *
008: * This program is free software; you can redistribute it and/or
009: * modify it under the terms of the GNU General Public License
010: * as published by the Free Software Foundation; either version 2
011: * of the License, or any later version.
012: *
013: * This program is distributed in the hope that it will be useful,
014: * but WITHOUT ANY WARRANTY; without even the implied warranty of
015: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
016: * GNU General Public License for more details.
017: *
018: * You should have received a copy of the GNU General Public License
019: * along with this program; if not, write to the Free Software
020: * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
021: */
022:
023: package org.gjt.sp.jedit;
024:
025: //{{{ Imports
026: import java.awt.datatransfer.*;
027: import java.awt.Toolkit;
028: import java.io.*;
029:
030: import org.gjt.sp.jedit.buffer.JEditBuffer;
031: import org.gjt.sp.jedit.gui.HistoryModel;
032: import org.gjt.sp.jedit.textarea.TextArea;
033: import org.gjt.sp.jedit.textarea.Selection;
034: import org.gjt.sp.util.Log;
035:
036: //}}}
037:
038: /**
039: * jEdit's registers are an extension of the clipboard metaphor.<p>
040: *
041: * A {@link Registers.Register} is string of text indexed by a
042: * single character. Typically the text is taken from selected buffer text
043: * and the index character is a keyboard character selected by the user.<p>
044: *
045: * This class defines a number of static methods
046: * that give each register the properties of a virtual clipboard.<p>
047: *
048: * Two classes implement the {@link Registers.Register} interface. A
049: * {@link Registers.ClipboardRegister} is tied to the contents of the
050: * system clipboard. jEdit assigns a
051: * {@link Registers.ClipboardRegister} to the register indexed under
052: * the character <code>$</code>. A
053: * {@link Registers.StringRegister} is created for registers assigned
054: * by the user. In addition, jEdit assigns <code>%</code> to
055: * the last text segment selected in the text area. On Windows this is a
056: * {@link Registers.StringRegister}, on Unix under Java 2 version 1.4, a
057: * {@link Registers.ClipboardRegister}.
058: *
059: * @author Slava Pestov
060: * @author John Gellene (API documentation)
061: * @version $Id: Registers.java 7367 2006-10-08 21:08:04Z kpouer $
062: */
063: public class Registers {
064: //{{{ copy() method
065: /**
066: * Copies the text selected in the text area into the specified register.
067: * This will replace the existing contents of the designated register.
068: *
069: * @param textArea The text area
070: * @param register The register
071: * @since jEdit 2.7pre2
072: */
073: public static void copy(TextArea textArea, char register) {
074: String selection = textArea.getSelectedText();
075: if (selection == null)
076: return;
077:
078: setRegister(register, selection);
079: HistoryModel.getModel("clipboard").addItem(selection);
080:
081: } //}}}
082:
083: //{{{ cut() method
084: /**
085: * Copies the text selected in the text area into the specified
086: * register, and then removes it from the buffer.
087: *
088: * @param textArea The text area
089: * @param register The register
090: * @since jEdit 2.7pre2
091: */
092: public static void cut(TextArea textArea, char register) {
093: if (textArea.isEditable()) {
094: String selection = textArea.getSelectedText();
095: if (selection == null)
096: return;
097:
098: setRegister(register, selection);
099: HistoryModel.getModel("clipboard").addItem(selection);
100:
101: textArea.setSelectedText("");
102: } else
103: textArea.getToolkit().beep();
104: } //}}}
105:
106: //{{{ append() method
107: /**
108: * Appends the text selected in the text area to the specified register,
109: * with a newline between the old and new text.
110: * @param textArea The text area
111: * @param register The register
112: */
113: public static void append(TextArea textArea, char register) {
114: append(textArea, register, "\n", false);
115: } //}}}
116:
117: //{{{ append() method
118: /**
119: * Appends the text selected in the text area to the specified register.
120: * @param textArea The text area
121: * @param register The register
122: * @param separator The separator to insert between the old and new text
123: */
124: public static void append(TextArea textArea, char register,
125: String separator) {
126: append(textArea, register, separator, false);
127: } //}}}
128:
129: //{{{ append() method
130: /**
131: * Appends the text selected in the text area to the specified register.
132: * @param textArea The text area
133: * @param register The register
134: * @param separator The text to insert between the old and new text
135: * @param cut Should the current selection be removed?
136: * @since jEdit 3.2pre1
137: */
138: public static void append(TextArea textArea, char register,
139: String separator, boolean cut) {
140: if (cut && !textArea.isEditable()) {
141: textArea.getToolkit().beep();
142: return;
143: }
144:
145: String selection = textArea.getSelectedText();
146: if (selection == null)
147: return;
148:
149: Register reg = getRegister(register);
150:
151: if (reg != null) {
152: String registerContents = reg.toString();
153: if (registerContents != null) {
154: if (registerContents.endsWith(separator))
155: selection = registerContents + selection;
156: else
157: selection = registerContents + separator
158: + selection;
159: }
160: }
161:
162: setRegister(register, selection);
163: HistoryModel.getModel("clipboard").addItem(selection);
164:
165: if (cut)
166: textArea.setSelectedText("");
167: } //}}}
168:
169: //{{{ paste() method
170: /**
171: * Insets the contents of the specified register into the text area.
172: * @param textArea The text area
173: * @param register The register
174: * @since jEdit 2.7pre2
175: */
176: public static void paste(TextArea textArea, char register) {
177: paste(textArea, register, false);
178: } //}}}
179:
180: //{{{ paste() method
181: /**
182: * Inserts the contents of the specified register into the text area.
183: * @param textArea The text area
184: * @param register The register
185: * @param vertical Vertical (columnar) paste
186: * @since jEdit 4.1pre1
187: */
188: public static void paste(TextArea textArea, char register,
189: boolean vertical) {
190: if (!textArea.isEditable()) {
191: textArea.getToolkit().beep();
192: return;
193: }
194:
195: Register reg = getRegister(register);
196:
197: if (reg == null) {
198: textArea.getToolkit().beep();
199: return;
200: }
201:
202: String selection = reg.toString();
203: if (selection == null) {
204: textArea.getToolkit().beep();
205: return;
206: }
207: JEditBuffer buffer = textArea.getBuffer();
208: try {
209: buffer.beginCompoundEdit();
210:
211: /* vertical paste */
212: if (vertical && textArea.getSelectionCount() == 0) {
213: int caret = textArea.getCaretPosition();
214: int caretLine = textArea.getCaretLine();
215: Selection.Rect rect = new Selection.Rect(caretLine,
216: caret, caretLine, caret);
217: textArea.setSelectedText(rect, selection);
218: caretLine = textArea.getCaretLine();
219:
220: if (caretLine != textArea.getLineCount() - 1) {
221: int startColumn = rect.getStartColumn(buffer);
222: int offset = buffer.getOffsetOfVirtualColumn(
223: caretLine + 1, startColumn, null);
224: if (offset == -1) {
225: buffer.insertAtColumn(caretLine + 1,
226: startColumn, "");
227: textArea.setCaretPosition(buffer
228: .getLineEndOffset(caretLine + 1) - 1);
229: } else {
230: textArea.setCaretPosition(buffer
231: .getLineStartOffset(caretLine + 1)
232: + offset);
233: }
234: }
235: } else /* Regular paste */
236: {
237: textArea.replaceSelection(selection);
238: }
239: } finally {
240: buffer.endCompoundEdit();
241: }
242: HistoryModel.getModel("clipboard").addItem(selection);
243: } //}}}
244:
245: //{{{ getRegister() method
246: /**
247: * Returns the specified register.
248: * @param name The name
249: */
250: public static Register getRegister(char name) {
251: if (name != '$' && name != '%') {
252: if (!loaded)
253: loadRegisters();
254: }
255:
256: if (registers == null || name >= registers.length)
257: return null;
258: else
259: return registers[name];
260: } //}}}
261:
262: //{{{ setRegister() method
263: /**
264: * Sets the specified register.
265: * @param name The name
266: * @param newRegister The new value
267: */
268: public static void setRegister(char name, Register newRegister) {
269: touchRegister(name);
270:
271: if (name >= registers.length) {
272: Register[] newRegisters = new Register[Math.min(1 << 16,
273: name << 1)];
274: System.arraycopy(registers, 0, newRegisters, 0,
275: registers.length);
276: registers = newRegisters;
277: }
278:
279: registers[name] = newRegister;
280: if (listener != null)
281: listener.registerChanged(name);
282: } //}}}
283:
284: //{{{ setRegister() method
285: /**
286: * Sets the specified register.
287: * @param name The name
288: * @param value The new value
289: */
290: public static void setRegister(char name, String value) {
291: touchRegister(name);
292: Register register = getRegister(name);
293: if (register != null) {
294: register.setValue(value);
295: if (listener != null)
296: listener.registerChanged(name);
297: } else
298: setRegister(name, new StringRegister(value));
299: } //}}}
300:
301: //{{{ clearRegister() method
302: /**
303: * Sets the value of the specified register to <code>null</code>.
304: * @param name The register name
305: */
306: public static void clearRegister(char name) {
307: if (name >= registers.length)
308: return;
309:
310: Register register = registers[name];
311: if (name == '$' || name == '%')
312: register.setValue("");
313: else {
314: registers[name] = null;
315: modified = true;
316: }
317: } //}}}
318:
319: //{{{ getRegisters() method
320: /**
321: * Returns an array of all available registers. Some of the elements
322: * of this array might be <code>null</code>.
323: */
324: public static Register[] getRegisters() {
325: if (!loaded)
326: loadRegisters();
327: return registers;
328: } //}}}
329:
330: //{{{ getRegisterStatusPrompt() method
331: /**
332: * Returns the status prompt for the given register action. Only
333: * intended to be called from <code>actions.xml</code>.
334: * @since jEdit 4.2pre2
335: */
336: public static String getRegisterStatusPrompt(String action) {
337: String registerNameString = getRegisterNameString();
338: return jEdit.getProperty("view.status." + action,
339: new String[] { registerNameString == null ? jEdit
340: .getProperty("view.status.no-registers")
341: : registerNameString });
342: } //}}}
343:
344: //{{{ getRegisterNameString() method
345: /**
346: * Returns a string of all defined registers, used by the status bar
347: * (eg, "a b $ % ^").
348: * @since jEdit 4.2pre2
349: */
350: public static String getRegisterNameString() {
351: if (!loaded)
352: loadRegisters();
353:
354: StringBuilder buf = new StringBuilder(registers.length << 1);
355: for (int i = 0; i < registers.length; i++) {
356: if (registers[i] != null) {
357: if (buf.length() != 0)
358: buf.append(' ');
359: buf.append((char) i);
360: }
361: }
362:
363: if (buf.length() == 0)
364: return null;
365: else
366: return buf.toString();
367: } //}}}
368:
369: //{{{ saveRegisters() method
370: public static void saveRegisters() {
371: if (!loaded || !modified)
372: return;
373:
374: if (saver != null) {
375: saver.saveRegisters();
376: modified = false;
377: }
378: } //}}}
379:
380: //{{{ setListener() method
381: public static void setListener(RegistersListener listener) {
382: Registers.listener = listener;
383: } //}}}
384:
385: //{{{ setSaver() method
386: public static void setSaver(RegisterSaver saver) {
387: Registers.saver = saver;
388: } //}}}
389:
390: //{{{ isLoading() method
391: public static boolean isLoading() {
392: return loading;
393: } //}}}
394:
395: //{{{ setLoading() method
396: public static void setLoading(boolean loading) {
397: Registers.loading = loading;
398: } //}}}
399:
400: //{{{ Private members
401: private static Register[] registers;
402: private static boolean loaded, loading;
403: private static RegisterSaver saver;
404: private static RegistersListener listener;
405: /**
406: * Flag that tell if a register has been modified (except for '%' and '$' registers that aren't
407: * saved to the xml file).
408: */
409: private static boolean modified;
410:
411: private Registers() {
412: }
413:
414: static {
415: registers = new Register[256];
416: Toolkit toolkit = Toolkit.getDefaultToolkit();
417: registers['$'] = new ClipboardRegister(toolkit
418: .getSystemClipboard());
419: Clipboard selection = toolkit.getSystemSelection();
420: if (selection != null)
421: registers['%'] = new ClipboardRegister(selection);
422: }
423:
424: //{{{ touchRegister() method
425: private static void touchRegister(char name) {
426: if (name == '%' || name == '$')
427: return;
428:
429: if (!loaded)
430: loadRegisters();
431:
432: if (!loading)
433: modified = true;
434: } //}}}
435:
436: //{{{ loadRegisters() method
437: private static void loadRegisters() {
438: if (saver != null) {
439: loaded = true;
440: saver.loadRegisters();
441: }
442: } //}}}
443:
444: //}}}
445:
446: //{{{ Inner classes
447:
448: //{{{ Register interface
449: /**
450: * A register.
451: */
452: public interface Register {
453: /**
454: * Converts to a string.
455: */
456: String toString();
457:
458: /**
459: * Sets the register contents.
460: */
461: void setValue(String value);
462: } //}}}
463:
464: //{{{ ClipboardRegister class
465: /**
466: * A clipboard register. Register "$" should always be an
467: * instance of this.
468: */
469: public static class ClipboardRegister implements Register {
470: Clipboard clipboard;
471:
472: public ClipboardRegister(Clipboard clipboard) {
473: this .clipboard = clipboard;
474: }
475:
476: /**
477: * Sets the clipboard contents.
478: */
479: public void setValue(String value) {
480: StringSelection selection = new StringSelection(value);
481: clipboard.setContents(selection, null);
482: }
483:
484: /**
485: * Returns the clipboard contents.
486: */
487: public String toString() {
488: try {
489:
490: if (false) {
491: /*
492: This is to debug clipboard problems.
493:
494: Apparently, jEdit is unable to copy text from clipbard into the current
495: text buffer if the clipboard was filles using the command
496: echo test | xselection CLIPBOARD -
497: under Linux. However, it seems that Java does not offer any
498: data flavor for this clipboard content (under J2RE 1.5.0_06-b05)
499: Thus, copying from clipboard seems to be plainly impossible.
500: */
501: Log
502: .log(Log.DEBUG, this ,
503: "clipboard.getContents(this)="
504: + clipboard
505: .getContents(this )
506: + '.');
507: debugListDataFlavors(clipboard.getContents(this ));
508: }
509:
510: String selection = (String) clipboard.getContents(this )
511: .getTransferData(DataFlavor.stringFlavor);
512:
513: boolean trailingEOL = selection.endsWith("\n")
514: || selection.endsWith(System
515: .getProperty("line.separator"));
516:
517: // Some Java versions return the clipboard
518: // contents using the native line separator,
519: // so have to convert it here
520: BufferedReader in = new BufferedReader(
521: new StringReader(selection));
522: StringBuilder buf = new StringBuilder();
523: String line;
524: while ((line = in.readLine()) != null) {
525: // broken Eclipse workaround!
526: // 24 Febuary 2004
527: if (line.endsWith("\0")) {
528: line = line.substring(0, line.length() - 1);
529: }
530: buf.append(line);
531: buf.append('\n');
532: }
533: // remove trailing \n
534: if (!trailingEOL && buf.length() != 0)
535: buf.setLength(buf.length() - 1);
536: return buf.toString();
537: } catch (Exception e) {
538: Log.log(Log.NOTICE, this , e);
539: return null;
540: }
541: }
542: } //}}}
543:
544: protected static void debugListDataFlavors(Transferable transferable) {
545: DataFlavor[] dataFlavors = transferable
546: .getTransferDataFlavors();
547:
548: for (int i = 0; i < dataFlavors.length; i++) {
549: DataFlavor dataFlavor = dataFlavors[i];
550:
551: Log.log(Log.DEBUG, Registers.class,
552: "debugListDataFlavors(): dataFlavor=" + dataFlavor
553: + '.');
554:
555: }
556:
557: if (dataFlavors.length == 0) {
558: Log.log(Log.DEBUG, Registers.class,
559: "debugListDataFlavors(): no dataFlavor supported.");
560: }
561: }
562:
563: //{{{ StringRegister class
564: /**
565: * Register that stores a string.
566: */
567: public static class StringRegister implements Register {
568: private String value;
569:
570: /**
571: * Creates a new string register.
572: * @param value The contents
573: */
574: public StringRegister(String value) {
575: this .value = value;
576: }
577:
578: /**
579: * Sets the register contents.
580: */
581: public void setValue(String value) {
582: this .value = value;
583: }
584:
585: /**
586: * Converts to a string.
587: */
588: public String toString() {
589: return value;
590: }
591:
592: /**
593: * Called when this register is no longer available. This
594: * implementation does nothing.
595: */
596: public void dispose() {
597: }
598: } //}}}
599:
600: //}}}
601: }
|