2
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
4
* Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
6
* The contents of this file are subject to the terms of either the GNU
7
* General Public License Version 2 only ("GPL") or the Common
8
* Development and Distribution License("CDDL") (collectively, the
9
* "License"). You may not use this file except in compliance with the
10
* License. You can obtain a copy of the License at
11
* http://www.netbeans.org/cddl-gplv2.html
12
* or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
13
* specific language governing permissions and limitations under the
14
* License. When distributing the software, include this License Header
15
* Notice in each file and include the License file at
16
* nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
17
* particular file as subject to the "Classpath" exception as provided
18
* by Sun in the GPL Version 2 section of the License file that
19
* accompanied this code. If applicable, add the following below the
20
* License Header, with the fields enclosed by brackets [] replaced by
21
* your own identifying information:
22
* "Portions Copyrighted [year] [name of copyright owner]"
26
* The Original Software is NetBeans. The Initial Developer of the Original
27
* Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
28
* Microsystems, Inc. All Rights Reserved.
30
* If you wish your version of this file to be governed by only the CDDL
31
* or only the GPL Version 2, indicate your decision by adding
32
* "[Contributor] elects to include this software in this distribution
33
* under the [CDDL or GPL Version 2] license." If you do not indicate a
34
* single choice of license, a recipient has the option to distribute
35
* your version of this file under either the CDDL, the GPL Version 2 or
36
* to extend the choice of license to its licensees as provided above.
37
* However, if you add GPL Version 2 code and therefore, elected the GPL
38
* Version 2 license, then the option applies only if the new code is
39
* made subject to such option by the copyright holder.
42
package org.netbeans.core.multiview;
44
import java.awt.BorderLayout;
45
import java.awt.event.ActionEvent;
46
import java.io.IOException;
47
import java.io.ObjectInput;
48
import java.io.ObjectOutput;
49
import java.io.Serializable;
50
import java.lang.reflect.InvocationHandler;
51
import java.lang.reflect.InvocationTargetException;
52
import java.lang.reflect.Method;
53
import java.lang.reflect.Proxy;
55
import java.util.logging.Level;
56
import java.util.logging.Logger;
58
import javax.swing.event.ChangeEvent;
59
import javax.swing.event.ChangeListener;
60
import javax.swing.undo.CannotRedoException;
61
import javax.swing.undo.CannotUndoException;
62
import org.netbeans.core.api.multiview.MultiViewPerspective;
63
import org.netbeans.core.multiview.MultiViewModel.ActionRequestObserverFactory;
64
import org.netbeans.core.multiview.MultiViewModel.ElementSelectionListener;
65
import org.netbeans.core.spi.multiview.CloseOperationHandler;
66
import org.netbeans.core.spi.multiview.CloseOperationState;
67
import org.netbeans.core.spi.multiview.MultiViewDescription;
68
import org.netbeans.core.spi.multiview.MultiViewElement;
69
import org.openide.awt.UndoRedo;
70
import org.openide.text.CloneableEditorSupport.Pane;
71
import org.openide.util.HelpCtx;
72
import org.openide.util.Lookup;
73
import org.openide.util.SharedClassObject;
74
import org.openide.windows.TopComponent;
76
/** Special subclass of TopComponent which shows and handles set of
77
* MultiViewElements, shows them in switchable toggle buttons style, along
78
* with toolbsrs af actions asociated with individual view elements.
81
* @author Dafe Simonek, Milos Kleint
85
public final class MultiViewPeer {
87
static final String MULTIVIEW_ID = "MultiView-"; //NOI18N
91
SelectionListener selListener;
92
CloseOperationHandler closeHandler;
93
transient MultiViewTopComponentLookup lookup;
95
private ActionRequestObserverFactory factory;
96
private MultiViewActionMap delegatingMap;
97
private boolean activated = false;
98
private Object editorSettingsListener;
99
private DelegateUndoRedo delegateUndoRedo;
101
public MultiViewPeer(TopComponent pr, ActionRequestObserverFactory fact) {
102
selListener = new SelectionListener();
105
editorSettingsListener = createEditorListener();
106
delegateUndoRedo = new DelegateUndoRedo();
112
public void setMultiViewDescriptions(MultiViewDescription[] descriptions, MultiViewDescription defaultDesc) {
114
model.removeElementSelectionListener(selListener);
116
model = new MultiViewModel(descriptions, defaultDesc, factory);
117
model.addElementSelectionListener(selListener);
118
tabs.setModel(model);
121
public void setCloseOperationHandler(CloseOperationHandler handler) {
122
closeHandler = handler;
125
void setDeserializedMultiViewDescriptions(MultiViewDescription[] descriptions,
126
MultiViewDescription defaultDesc, Map<MultiViewDescription, MultiViewElement> existingElements) {
128
model.removeElementSelectionListener(selListener);
130
model = new MultiViewModel(descriptions, defaultDesc, factory, existingElements);
131
model.addElementSelectionListener(selListener);
132
tabs.setModel(model);
136
* for use in tests only!!!!!
138
MultiViewModel getModel() {
143
void initComponents() {
145
peer.setLayout(new BorderLayout());
146
tabs = new TabsComponent(isToolbarVisible());
148
ActionMap map = peer.getActionMap();
149
Action act = new AccessTogglesAction();
150
map.put("NextViewAction", new GetRightEditorAction()); //NOI18N
151
map.put("PreviousViewAction", new GetLeftEditorAction()); //NOI18N
152
map.put("accesstoggles", act); //NOI18N
153
InputMap input = peer.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
154
KeyStroke stroke = KeyStroke.getKeyStroke("control F10"); //NOI18N
155
input.put(stroke, "accesstoggles"); //NOI18N
156
// stroke = (KeyStroke)new GetLeftEditorAction().getValue(Action.ACCELERATOR_KEY);
157
// input.put(stroke, "getLeftEditor");
158
input = peer.getInputMap(JComponent.WHEN_FOCUSED);
159
input.put(stroke, "accesstoggles"); //NOI18N
161
peer.putClientProperty("MultiViewBorderHack.topOffset", new Integer(tabs.getPreferredSize().height - 1));
164
// It is necessary so the old actions (clone and close from org.openide.actions package) remain working.
166
private void initActionMap() {
167
delegatingMap = new MultiViewActionMap(peer, new ActionMap ());
168
if(peer instanceof TopComponent.Cloneable) {
169
delegatingMap.put("cloneWindow", new javax.swing.AbstractAction() { // NOI18N
170
public void actionPerformed(ActionEvent evt) {
171
TopComponent cloned = ((TopComponent.Cloneable)
172
peer).cloneComponent();
174
cloned.requestActive();
178
delegatingMap.put("closeWindow", new javax.swing.AbstractAction() { // NOI18N
179
public void actionPerformed(ActionEvent evt) {
183
peer.setActionMap(delegatingMap);
186
void peerComponentClosed() {
187
Iterator it = model.getCreatedElements().iterator();
188
while (it.hasNext()) {
189
MultiViewElement el = (MultiViewElement)it.next();
190
model.markAsHidden(el);
191
el.componentClosed();
196
void peerComponentShowing() {
197
MultiViewElement el = model.getActiveElement();
198
el.componentShowing();
199
delegatingMap.setDelegateMap(el.getVisualRepresentation().getActionMap());
200
((MultiViewTopComponentLookup)peer.getLookup()).setElementLookup(el.getLookup());
201
JComponent jc = el.getToolbarRepresentation();
202
assert jc != null : "MultiViewElement " + el.getClass() + " returns null as toolbar component."; //NOI18N
204
tabs.setInnerToolBar(jc);
205
tabs.setToolbarBarVisible(isToolbarVisible());
206
if (editorSettingsListener != null) {
207
addEditorListener(editorSettingsListener);
211
void peerComponentHidden() {
212
model.getActiveElement().componentHidden();
213
if (editorSettingsListener != null) {
214
removeEditorListener(editorSettingsListener);
218
void peerComponentDeactivated() {
220
model.getActiveElement().componentDeactivated();
223
boolean isActivated() {
227
void peerComponentActivated() {
229
model.getActiveElement().componentActivated();
232
void peerComponentOpened() {
233
showCurrentElement(true);
234
tabs.setToolbarBarVisible(isToolbarVisible());
237
boolean requestFocusInWindow() {
238
return model.getActiveElement().getVisualRepresentation().requestFocusInWindow();
241
void requestFocus() {
242
model.getActiveElement().getVisualRepresentation().requestFocus();
246
* hides the old element when switching elements.
248
void hideElement(MultiViewDescription desc) {
250
MultiViewElement el = model.getElementForDescription(desc);
251
el.componentHidden();
256
void showCurrentElement() {
257
showCurrentElement(false);
261
* shows the new element after switching elements.
264
private void showCurrentElement(boolean calledFromComponentOpened) {
265
MultiViewElement el = model.getActiveElement();
266
MultiViewDescription desc = model.getActiveDescription();
268
// TODO display name is not a good unique id..
269
// also consider a usecase where multiple elements point to a single visual component.
270
//. eg. property sheet uses same component and only changes model.
271
// in this case we probably should not remove and add the component from awt hierarchy
272
tabs.switchToCard(el, desc.getDisplayName());
273
peer.setIcon(desc.getIcon());
274
// the first time the component is shown, we need to call componentOpened() on it to be in synch with current
275
// TopComponent behaviour?
276
if (peer.isOpened() || calledFromComponentOpened) {
277
if (!model.wasShownBefore(el)) {
278
el.componentOpened();
279
model.markAsShown(el);
282
if (!calledFromComponentOpened) {
284
// replace isOpened() with isVisible() because some multiview don't have to be directly referenced form the
286
if (peer.isVisible()) {
287
el.componentShowing();
289
// should we really set the stuff only when not called from componentOpened()? maybe it's better to call it twice sometimes.
290
// if we don't call it here for opened but not showing component, then the map, lookup and nodes will not be initialized properly.
292
delegatingMap.setDelegateMap(el.getVisualRepresentation().getActionMap());
293
((MultiViewTopComponentLookup)peer.getLookup()).setElementLookup(el.getLookup());
295
if (peer.isVisible()) {
296
tabs.setInnerToolBar(el.getToolbarRepresentation());
297
tabs.setToolbarBarVisible(isToolbarVisible());
306
* merge action for the topcomponent and the enclosed MultiViewElement..
309
Action[] peerGetActions(Action[] superActions) {
310
//TEMP don't delegate to element's actions..
311
Action[] acts = model.getActiveElement().getActions();
312
for (int i = 0; i < acts.length; i++) {
313
Action act = acts[i];
314
for (int j = 0 ; j < superActions.length; j++) {
315
Action superact = superActions[j];
316
if (superact != null && act != null && superact.getClass().equals(act.getClass())) {
317
// these are the default topcomponent actions.. we need to replace them
318
// in order to have the correct context.
319
acts[i] = superActions[j];
327
MultiViewHandlerDelegate getMultiViewHandlerDelegate() {
328
// TODO have one handler only or create a new one each time?
329
return new MVTCHandler();
333
* Delegates the value to the element descriptions.
335
int getPersistenceType() {
337
// should also take the opened/created elements into account.
338
// no need to serialize the tc when the element that want to be serialized, was not
339
// even opened?!? but maybe handle this during the serialization proceess, avoid creating
340
// the element when serializing.
341
MultiViewDescription[] descs = model.getDescriptions();
342
int type = TopComponent.PERSISTENCE_NEVER;
343
for (int i = 0; i < descs.length; i++) {
344
if (!(descs[i] instanceof Serializable)) {
345
Logger.getLogger(MultiViewTopComponent.class.getName()).warning(
346
"The MultiviewDescription instance " + descs[i].getClass() + " is not serializable. Cannot persist TopComponent.");
347
type = TopComponent.PERSISTENCE_NEVER;
350
if (descs[i].getPersistenceType() == TopComponent.PERSISTENCE_ALWAYS) {
351
type = descs[i].getPersistenceType();
352
// cannot ge any better than that.
354
if (descs[i].getPersistenceType() == TopComponent.PERSISTENCE_ONLY_OPENED &&
355
type != TopComponent.PERSISTENCE_ALWAYS) {
356
type = descs[i].getPersistenceType();
364
String preferredID() {
365
StringBuffer retValue = new StringBuffer(MULTIVIEW_ID);
366
MultiViewDescription[] descs = model.getDescriptions();
367
for (int i = 0; i < descs.length; i++) {
368
retValue.append(descs[i].preferredID());
369
retValue.append("|"); //NOI18N
371
return retValue.toString();
376
/** Serialize this top component.
377
* Subclasses wishing to store state must call the super method, then write to the stream.
378
* @param out the stream to serialize to
380
void peerWriteExternal (ObjectOutput out) throws IOException {
381
if (closeHandler != null) {
382
if (closeHandler instanceof Serializable) {
383
out.writeObject(closeHandler);
385
//TODO some warning to the SPI programmer
386
Logger.getAnonymousLogger().info(
387
"The CloseOperationHandler isn not serializable. MultiView component id=" + preferredID());
390
MultiViewDescription[] descs = model.getDescriptions();
391
MultiViewDescription curr = model.getActiveDescription();
393
for (int i = 0; i < descs.length; i++) {
394
out.writeObject(descs[i]);
395
if (descs[i].getPersistenceType() != TopComponent.PERSISTENCE_NEVER) {
396
// only those requeTopsted and previously created elements are serialized.
397
MultiViewElement elem = model.getElementForDescription(descs[i], false);
398
if (elem != null && elem instanceof Serializable) {
399
out.writeObject(elem);
402
if (descs[i] == curr) {
406
out.writeObject(new Integer(currIndex));
410
/** Deserialize this top component.
411
* Subclasses wishing to store state must call the super method, then read from the stream.
412
* @param in the stream to deserialize from
414
void peerReadExternal (ObjectInput in) throws IOException, ClassNotFoundException {
415
ArrayList<MultiViewDescription> descList = new ArrayList<MultiViewDescription>();
416
HashMap<MultiViewDescription, MultiViewElement> map = new HashMap<MultiViewDescription, MultiViewElement>();
418
CloseOperationHandler close = null;
420
Object obj = in.readObject();
421
if (obj instanceof MultiViewDescription) {
422
descList.add((MultiViewDescription)obj);
424
else if (obj instanceof MultiViewElement) {
425
map.put(descList.get(descList.size() - 1), (MultiViewElement)obj);
427
else if (obj instanceof Integer) {
428
Integer integ = (Integer)obj;
429
current = integ.intValue();
432
if (obj instanceof CloseOperationHandler) {
433
close = (CloseOperationHandler)obj;
437
//TODO some warning to the SPI programmer
438
close = SpiAccessor.DEFAULT.createDefaultCloseHandler();
440
setCloseOperationHandler(close);
441
// now that we've read everything, we should set it correctly.
442
MultiViewDescription[] descs = new MultiViewDescription[descList.size()];
443
descs = descList.toArray(descs);
444
MultiViewDescription currDesc = descs[current];
445
setDeserializedMultiViewDescriptions(descs, currDesc, map);
449
private Action[] getDefaultTCActions() {
450
//TODO for each suppoerted peer have one entry..
451
if (peer instanceof MultiViewTopComponent) {
452
return ((MultiViewTopComponent)peer).getDefaultTCActions();
454
return new Action[0];
459
JEditorPane getEditorPane() {
461
MultiViewElement el = model.getActiveElement();
462
if (el != null && el.getVisualRepresentation() instanceof Pane) {
463
Pane pane = (Pane)el.getVisualRepresentation();
464
return pane.getEditorPane();
470
HelpCtx getHelpCtx() {
471
return model.getActiveDescription().getHelpCtx();
475
* Get the undo/redo support for this component.
476
* The default implementation returns a dummy support that cannot
479
* @return undoable edit for this component
481
UndoRedo peerGetUndoRedo() {
482
return delegateUndoRedo;
485
private UndoRedo privateGetUndoRedo() {
486
return model.getActiveElement().getUndoRedo() != null ? model.getActiveElement().getUndoRedo() : UndoRedo.NONE;
490
* This method is called when this <code>TopComponent</code> is about to close.
491
* Delegates to CloseOperationHandler.
494
Collection col = model.getCreatedElements();
495
Iterator it = col.iterator();
496
Collection badOnes = new ArrayList();
497
while (it.hasNext()) {
498
MultiViewElement el = (MultiViewElement)it.next();
499
CloseOperationState state = el.canCloseElement();
500
if (!state.canClose()) {
504
if (badOnes.size() > 0) {
505
CloseOperationState[] states = new CloseOperationState[badOnes.size()];
506
states = (CloseOperationState[])badOnes.toArray(states);
507
return closeHandler.resolveCloseOperation(states);
512
// from CloneableEditor.Pane
513
public void updateName() {
514
// is called before setMultiViewDescriptions() need to check for null.
516
MultiViewElement el = model.getActiveElement();
517
if (el.getVisualRepresentation() instanceof Pane) {
518
Pane pane = (Pane)el.getVisualRepresentation();
520
peer.setDisplayName(pane.getComponent().getDisplayName());
526
public Lookup getLookup() {
527
if (lookup == null) {
528
lookup = new MultiViewTopComponentLookup(delegatingMap);
534
//-------------------------------------------------------------------------------
535
//---------------------------------------------------------------------------
536
//--------------- editor reflection stuff to retrieve the toolbar visibility setting
537
//----------------------------------------------------------------------------------
538
void addEditorListener(Object listener) {
540
final ClassLoader loader = (ClassLoader)Lookup.getDefault().lookup(ClassLoader.class);
541
Class settingsClass = Class.forName(
542
"org.netbeans.editor.Settings", false, loader); //NOI18N
543
Class listenerClass = Class.forName(
544
"org.netbeans.editor.SettingsChangeListener", false, loader); //NOI18N
545
Method addSettingsListener = settingsClass.getMethod(
546
"addSettingsChangeListener",new Class[ ] { listenerClass });//NOI18N
547
addSettingsListener.invoke(settingsClass, new Object[] { listener });
548
} catch (Throwable t) {
553
void removeEditorListener(Object listener) {
555
final ClassLoader loader = (ClassLoader)Lookup.getDefault().lookup(ClassLoader.class);
556
Class settingsClass = Class.forName(
557
"org.netbeans.editor.Settings", false, loader); //NOI18N
558
Class listenerClass = Class.forName(
559
"org.netbeans.editor.SettingsChangeListener", false, loader); //NOI18N
560
Method addSettingsListener = settingsClass.getMethod(
561
"removeSettingsChangeListener",new Class[ ] { listenerClass });//NOI18N
562
addSettingsListener.invoke(settingsClass, new Object[] { listener });
563
} catch (Throwable t) {
568
Object createEditorListener() {
570
final ClassLoader loader = (ClassLoader)Lookup.getDefault().lookup(ClassLoader.class);
573
listenerClass = Class.forName("org.netbeans.editor.SettingsChangeListener", false, loader); //NOI18N
574
} catch (ClassNotFoundException ex) {
575
Logger.getLogger(MultiViewPeer.class.getName()).log(Level.CONFIG, "Disabling interaction with editor/lib", ex); // NOI18N
578
InvocationHandler ih = new InvocationHandler() {
579
public Object invoke(Object proxy, Method method, Object[] args) {
580
SwingUtilities.invokeLater(new Runnable() {
582
tabs.setToolbarBarVisible(isToolbarVisible());
588
return Proxy.newProxyInstance(loader,
589
new Class[] { listenerClass }, ih);
590
} catch (Throwable t) {
591
Logger.getLogger(MultiViewPeer.class.getName()).log(Level.WARNING, null, t);
596
boolean isToolbarVisible() {
597
//TODO need some way to restrict the validity of this swicth only to multiviews that contain
598
// sources in some form..
599
JEditorPane pane = getEditorPane();
601
Object obj = pane.getActionMap().get("toggle-toolbar");
608
SharedClassObject option = null;
609
ClassLoader loader = (ClassLoader) Lookup.getDefault().lookup(ClassLoader.class);
610
if (loader == null) {
611
loader = MultiViewPeer.class.getClassLoader().getSystemClassLoader();
614
Class editorBaseOption = Class.forName("org.netbeans.modules.editor.options.BaseOptions", true,
616
option = SharedClassObject.findObject(editorBaseOption);
617
} catch (ClassNotFoundException ex) {
618
ex.printStackTrace();
620
if (option != null) {
622
Method is = option.getClass().getMethod("isToolbarVisible", new Class[0]);
624
ret = is.invoke(option, new Object[0]);
625
if (ret instanceof Boolean) {
626
return ((Boolean)ret).booleanValue();
628
} catch (IllegalArgumentException ex) {
629
ex.printStackTrace();
630
} catch (SecurityException ex) {
631
ex.printStackTrace();
632
} catch (InvocationTargetException ex) {
633
ex.printStackTrace();
634
} catch (NoSuchMethodException ex) {
635
ex.printStackTrace();
636
} catch (IllegalAccessException ex) {
637
ex.printStackTrace();
645
public String toString() {
646
return "[model=" + model + "]"; // NOI18N
649
* notification from the model that the selection changed.
651
private class SelectionListener implements ElementSelectionListener {
653
public void selectionChanged(MultiViewDescription oldOne, MultiViewDescription newOne) {
655
MultiViewElement el = model.getElementForDescription(oldOne);
656
el.componentDeactivated();
659
showCurrentElement();
660
delegateUndoRedo.updateListeners(model.getElementForDescription(oldOne),
661
model.getElementForDescription(newOne));
664
public void selectionActivatedByButton() {
665
MultiViewElement elem = model.getActiveElement();
666
elem.getVisualRepresentation().requestFocus();
667
elem.componentActivated();
672
private class MVTCHandler implements MultiViewHandlerDelegate {
673
private MultiViewPerspective[] perspectives = null;
675
public MultiViewPerspective[] getDescriptions() {
676
return model.getPerspectives();
679
public MultiViewPerspective getSelectedDescription() {
680
return model.getSelectedPerspective();
683
public void requestActive(MultiViewPerspective pers) {
684
MultiViewDescription desc = Accessor.DEFAULT.extractDescription(pers);
685
if (model.getActiveDescription() != desc) {
686
tabs.changeActiveManually(desc);
687
model.getActiveElement().componentActivated();
691
public void requestVisible(MultiViewPerspective pers) {
692
MultiViewDescription desc = Accessor.DEFAULT.extractDescription(pers);
693
tabs.changeVisibleManually(desc);
696
// public MultiViewPerspectiveComponent getElementForDescription(MultiViewPerspective pers) {
697
// MultiViewDescription desc = Accessor.DEFAULT.extractDescription(pers);
698
// return model.getMVComponentForDescription(desc);
705
private class AccessTogglesAction extends AbstractAction {
707
AccessTogglesAction() {
708
// putValue(Action.NAME, "AccessToggleMenu");
709
putValue(Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke("control F10")); //NOI18N
712
public void actionPerformed(ActionEvent e) {
713
tabs.requestFocusForSelectedButton();
718
private class DelegateUndoRedo implements UndoRedo {
720
private List listeners = new ArrayList();
722
public boolean canUndo() {
723
return privateGetUndoRedo().canUndo();
726
public boolean canRedo() {
727
return privateGetUndoRedo().canRedo();
730
public void undo() throws CannotUndoException {
731
privateGetUndoRedo().undo();
734
public void redo() throws CannotRedoException {
735
privateGetUndoRedo().redo();
738
public void addChangeListener(ChangeListener l) {
740
privateGetUndoRedo().addChangeListener(l);
743
public void removeChangeListener(ChangeListener l) {
745
privateGetUndoRedo().removeChangeListener(l);
748
public String getUndoPresentationName() {
749
return privateGetUndoRedo().getUndoPresentationName();
752
public String getRedoPresentationName() {
753
return privateGetUndoRedo().getRedoPresentationName();
756
private void fireElementChange() {
757
Iterator it = new ArrayList(listeners).iterator();
758
while (it.hasNext()) {
759
ChangeListener elem = (ChangeListener) it.next();
760
ChangeEvent event = new ChangeEvent(this);
761
elem.stateChanged(event);
766
void updateListeners(MultiViewElement old, MultiViewElement fresh) {
767
Iterator it = listeners.iterator();
768
while (it.hasNext()) {
769
ChangeListener elem = (ChangeListener) it.next();
770
if (old.getUndoRedo() != null) {
771
old.getUndoRedo().removeChangeListener(elem);
773
if (fresh.getUndoRedo() != null) {
774
fresh.getUndoRedo().addChangeListener(elem);
b'\\ No newline at end of file'