Package de.sciss.meloncillo.gui

Source Code of de.sciss.meloncillo.gui.ObserverPalette$SessionCollectionListener

/*
*  ObserverPalette.java
*  Meloncillo
*
*  Copyright (c) 2004-2008 Hanns Holger Rutz. All rights reserved.
*
*  This software is free software; you can redistribute it and/or
*  modify it under the terms of the GNU General Public License
*  as published by the Free Software Foundation; either
*  version 2, june 1991 of the License, or (at your option) any later version.
*
*  This software is distributed in the hope that it will be useful,
*  but WITHOUT ANY WARRANTY; without even the implied warranty of
*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
*  General Public License for more details.
*
*  You should have received a copy of the GNU General Public
*  License (gpl.txt) along with this software; if not, write to the Free Software
*  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*
*
*  For further information, please contact Hanns Holger Rutz at
*  contact@sciss.de
*
*
*  Changelog:
*    13-Jun-04   now uses NumberFields. New tabs for Transmitter + Timeline.
*          Removed SurfaceListener interface. showTab() and showCursorInfo() methods.
*    31-Jul-04   DynamicAncestorAdapter replaces DynamicComponentAdapter
*      24-Dec-04   extends BasicPalette
*      27-Dec-04   added online help
*    19-Mar-05  fixed wrong tab switch (timeline events), removed automatic frame size packing
*/

package de.sciss.meloncillo.gui;

import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.Point2D;
import java.util.List;

import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTabbedPane;
import javax.swing.JTextField;
import javax.swing.SwingConstants;
import javax.swing.WindowConstants;

import de.sciss.util.DefaultUnitTranslator;
import de.sciss.util.Param;
import de.sciss.util.ParamSpace;

import de.sciss.app.AbstractApplication;
import de.sciss.app.AbstractCompoundEdit;
import de.sciss.app.AbstractWindow;
import de.sciss.app.Application;
import de.sciss.app.DocumentListener;
import de.sciss.app.DynamicListening;
import de.sciss.common.AppWindow;
import de.sciss.gui.AbstractWindowHandler;
import de.sciss.gui.ParamField;
import de.sciss.gui.SpringPanel;
import de.sciss.io.Span;
import de.sciss.meloncillo.Main;
import de.sciss.meloncillo.edit.BasicCompoundEdit;
import de.sciss.meloncillo.edit.EditSetSessionObjectName;
import de.sciss.meloncillo.session.BasicSessionCollection;
import de.sciss.meloncillo.session.Session;
import de.sciss.meloncillo.session.SessionCollection;
import de.sciss.meloncillo.session.SessionObject;
import de.sciss.meloncillo.session.SessionObjectTable;
import de.sciss.meloncillo.timeline.TimelineEvent;
import de.sciss.meloncillo.timeline.TimelineListener;

/**
*  The <code>ObserverPalette</code> is a
*  GUI component that displays context sensitive
*  data and is closely linked to the user's
*  mouse activities. It contains tabbed panels
*  for general cursor information and information
*  concerning receivers, transmitters and the
*  timeline. Depending on the tab, the data is
*  presented in editable text or number fields.
<p>
*  The cursor info pane is 'passive' because in this way
*  it is easily expandible to display data of new
*  components. The interested components are hence
*  resonsible for calling <code>showCursorInfo</code>.
*
@author    Hanns Holger Rutz
@version  0.75, 19-Jun-08
*/
public class ObserverPalette
extends AppWindow
implements  ParamField.Listener, TimelineListener, DynamicListening, DocumentListener,
      ActionListener, SwingConstants
{
  private Session            doc          = null;
 
  private JLabel[]          lbCursorInfo;
  private final ParamField      ggTimelineStart, ggTimelineStop, ggTimelineLen;
  private final DefaultUnitTranslator  timeTrans;
  private final JTabbedPane      ggTabPane;
  private final LockButton      ggLockStop, ggLockLen;

  public static final int CURSOR_TAB    = 0;
  public static final int RECEIVER_TAB  = 1;
  public static final int TRANSMITTER_TAB  = 2;
  public static final int GROUP_TAB    = 3;
  public static final int TIMELINE_TAB  = 4;
 
  public static final int  NUM_CURSOR_ROWS    = 5;

  private final String[] TAB_NAMES  = { "observerReceiver", "observerTransmitter", "observerGroup" };
 
  private final SessionCollectionListener[] selColL  = new SessionCollectionListener[ 3 ];
  private final JTextField[]        ggNames    = new JTextField[ 3 ];
  private final SessionObjectTable[]    ggTables  = new SessionObjectTable[ 3 ];
 
  private boolean isListening  = false;
 
  /**
   *  Constructs a new <code>ObserverPalette</code>
   *
   *  @param  root  application root
   *  @param  doc    session document
   */
  public ObserverPalette()
  {
    super( PALETTE );
   
    final Application app = AbstractApplication.getApplication();
 
    setTitle( app.getResourceString( "paletteObserver" ));
// EEE
//    setResizable( false );
   
    final Container    cp    = getContentPane();
    JPanel        c;
    SpringPanel      p;
    JLabel        lb;
    GridBagLayout    lay;
    GridBagConstraints  con;

    ggTabPane = new JTabbedPane();
        ggTabPane.setTabLayoutPolicy( JTabbedPane.WRAP_TAB_LAYOUT );

    // ----- cursor tab ------
    c        = new JPanel();
    lay        = new GridBagLayout();
    con        = new GridBagConstraints();
    con.insets    = new Insets( 2, 2, 2, 2 );
    c.setLayout( lay );
    con.weightx    = 1.0;
    con.gridwidth   = GridBagConstraints.REMAINDER;
    lbCursorInfo  = new JLabel[ NUM_CURSOR_ROWS ];
    for( int i = 0; i < NUM_CURSOR_ROWS; i++ ) {
      lb      = new JLabel();
      lay.setConstraints( lb, con );
      c.add( lb );
      lbCursorInfo[i] = lb;
    }
    ggTabPane.addTab( app.getResourceString( "observerCursor" ), null, c, null );
       
    // ----- session objects tab ------
    for( int i = 0; i < 3; i++ ) {
      c        = new JPanel();
      lay        = new GridBagLayout();
      con        = new GridBagConstraints();
      con.insets    = new Insets( 2, 2, 2, 2 );
      c.setLayout( lay );
      lb        = new JLabel( app.getResourceString( "labelName" ), JLabel.RIGHT );
      con.weightx    = 0.0;
      con.gridwidth   = 1;
      lay.setConstraints( lb, con );
      c.add( lb );
      ggNames[ i = new JTextField( 12 );
      con.weightx    = 1.0;
      con.gridwidth   = GridBagConstraints.REMAINDER;
      lay.setConstraints( ggNames[ i ], con );
      lb.setLabelFor( ggNames[ i ]);
      c.add( ggNames[ i ]);
      con.fill    = GridBagConstraints.BOTH;
      con.weighty    = 1.0;
      ggTables[ i = new SessionObjectTable();
      lay.setConstraints( ggTables[ i ], con );
      c.add( ggTables[ i ]);
      ggTabPane.addTab( app.getResourceString( TAB_NAMES[ i ]), null, c, null );
//      HelpGlassPane.setHelp( c, HELP_NAMES[ i ]);  // EEE
     
//      switch( i ) {
//      case 0:
//        scSel  = doc.selectedReceivers;
//        scAll  = doc.receivers;
//        break;
//      case 1:
//        scSel  = doc.selectedTransmitters;
//        scAll  = doc.transmitters;
//        break;
//      case 2:
//        scSel  = doc.selectedGroups;
//        scAll  = doc.groups;
//        break;
//      default:
//        assert false : i;
//        scSel  = null;
//        scAll  = null;
//        break;
//      }
//     
//      new SessionCollectionListener( scSel, scAll, i + 1, ggName, ggTable, getMyRooty() );
    }

    // ----- timeline tab ------
    timeTrans    = new DefaultUnitTranslator();
   
    p        = new SpringPanel( 4, 2, 4, 2 );
    lb        = new JLabel( app.getResourceString( "observerStart" ), RIGHT );
    p.gridAdd( lb, 1, 0 );
    ggTimelineStart  = new ParamField( timeTrans );
    ggTimelineStart.addSpace( ParamSpace.spcTimeHHMMSS );
    ggTimelineStart.addSpace( ParamSpace.spcTimeSmps );
    ggTimelineStart.addSpace( ParamSpace.spcTimeMillis );
    ggTimelineStart.addSpace( ParamSpace.spcTimePercentR );
    ggTimelineStart.addListener( this );
    p.gridAdd( ggTimelineStart, 2, 0 );
    lb.setLabelFor( ggTimelineStart );
   
    ggLockStop    = new LockButton( true );
    ggLockStop.addActionListener( this );
    p.gridAdd( ggLockStop, 0, 1 );
    lb        = new JLabel( app.getResourceString( "observerStop" ), RIGHT );
    p.gridAdd( lb, 1, 1 );
    ggTimelineStop  = new ParamField( timeTrans );
    ggTimelineStop.addSpace( ParamSpace.spcTimeHHMMSS );
    ggTimelineStop.addSpace( ParamSpace.spcTimeSmps );
    ggTimelineStop.addSpace( ParamSpace.spcTimeMillis );
    ggTimelineStop.addSpace( ParamSpace.spcTimePercentR );
    ggTimelineStop.addListener( this );
    p.gridAdd( ggTimelineStop, 2, 1 );
    lb.setLabelFor( ggTimelineStop );

    ggLockLen    = new LockButton( true );
    ggLockLen.addActionListener( this );
    p.gridAdd( ggLockLen, 0, 2 );
    lb        = new JLabel( app.getResourceString( "observerLen" ), RIGHT );
    p.gridAdd( lb, 1, 2 );
    ggTimelineLen  = new ParamField( timeTrans );
    ggTimelineLen.addSpace( ParamSpace.spcTimeHHMMSS );
    ggTimelineLen.addSpace( ParamSpace.spcTimeSmps );
    ggTimelineLen.addSpace( ParamSpace.spcTimeMillis );
    ggTimelineLen.addSpace( ParamSpace.spcTimePercentR );
    ggTimelineLen.addListener( this );
    p.gridAdd( ggTimelineLen, 2, 2 );
    lb.setLabelFor( ggTimelineLen );
   
    p.makeCompactGrid( false, false );
    ggTabPane.addTab( app.getResourceString( "observerTimeline" ), null, p, null );
    cp.add( BorderLayout.CENTER, ggTabPane );
   
    AbstractWindowHandler.setDeepFont( ggTabPane );
   
    // --- Listener ---
        addDynamicListening( this );
   
    app.getDocumentHandler().addDocumentListener( this );

    addListener( new AbstractWindow.Adapter() {
      public void windowClosing( AbstractWindow.Event e )
      {
        dispose();
      }
    });
    setDefaultCloseOperation( WindowConstants.DO_NOTHING_ON_CLOSE ); // window listener see above!
   
    setPreferredSize( new Dimension( 272, 180 ));
   
    init();
    app.addComponent( Main.COMP_OBSERVER, this );
  }
 
  protected boolean autoUpdatePrefs()
  {
    return true;
  }

  protected Point2D getPreferredLocation()
  {
    return new Point2D.Float( 0.95f, 0.8f );
  }

  protected boolean alwaysPackSize()
  {
    return false;
  }

  public void dispose()
  {
    AbstractApplication.getApplication().removeComponent( Main.COMP_OBSERVER );
    super.dispose();
  }
 
  /**
   *  Switch the display to a specific
   *  tab, where zero is the cursor info tab.
   *
   *  @param  tabIndex  the index of the tab to show
   */
  public void showTab( int tabIndex )
  {
    if( ggTabPane.getSelectedIndex() != tabIndex ) {
      ggTabPane.setSelectedIndex( tabIndex );
    }
  }
 
  public int getShownTab()
  {
    return ggTabPane.getSelectedIndex();
  }

  /**
   *  Display information in the cursor pane.
   *  It's up to the caller what kind of information
   *  is displayed. This method simply displays
   *  up to four lines of text. This method doesn't
   *  switch the display to the cursor pane.
   *
   *  @param  info  an array of zero to four strings
   *          which will be displayed in the
   *          cursor tab.
   */
  public void showCursorInfo( String[] info )
  {
    int i, j;
   
    j = Math.min( NUM_CURSOR_ROWS, info.length );
   
    for( i = 0; i < j; i++ ) {
      lbCursorInfo[i].setText( info[i] );
    }
    for( ; i < NUM_CURSOR_ROWS; i++ ) {
      lbCursorInfo[i].setText( "" );
    }
  }

  // attempts shared on DOOR_TIME
  private void updateTimeline()
  {
    if( doc != null ) {

      final Span  span  = doc.timeline.getSelectionSpan();
      final double rate  = doc.timeline.getRate();

      timeTrans.setLengthAndRate( doc.timeline.getLength(), rate );
      ggTimelineStart.setValue( new Param( span.getStart(), ParamSpace.spcTimeSmps.unit ));
      ggTimelineStop.setValue( new Param( span.getStop(), ParamSpace.spcTimeSmps.unit ));
      ggTimelineLen.setValue( new Param( span.getLength(), ParamSpace.spcTimeSmps.unit ));
      if( !ggTimelineStart.isEnabled() ) ggTimelineStart.setEnabled( true );
      if( !ggTimelineStop.isEnabled() )  ggTimelineStop.setEnabled( true );
      if( !ggTimelineLen.isEnabled() )  ggTimelineLen.setEnabled( true );
     
    } else {
     
      ggTimelineStart.setEnabled( false );
      ggTimelineStop.setEnabled( false );
      ggTimelineLen.setEnabled( false );
    }
  }

//   ---------------- ActionListener interface ----------------

  public void actionPerformed( ActionEvent e )
  {
    if( e.getSource() == ggLockStop ) {
      if( ggLockStop.isLocked() && ggLockLen.isLocked() ) {
        ggLockLen.setLocked( false );
      }   
    } else if( e.getSource() == ggLockLen ) {
      if( ggLockStop.isLocked() && ggLockLen.isLocked() ) {
        ggLockStop.setLocked( false );
      }   
    }
  }

  // ---------------- DynamicListening interface ----------------

    public void startListening()
    {
      isListening = true;
    doc = (Session) AbstractApplication.getApplication().getDocumentHandler().getActiveDocument();
    if( doc != null ) {
      doc.timeline.addTimelineListener( this );
      selColL[ 0 ].startListening();
      selColL[ 1 ].startListening();
      selColL[ 2 ].startListening();
    }
    updateTimeline();
    }

    public void stopListening()
    {
      isListening = false;
    if( doc != null ) {
      doc.timeline.removeTimelineListener( this );
      selColL[ 0 ].stopListening();
      selColL[ 1 ].stopListening();
      selColL[ 2 ].stopListening();
    }
    }

// ---------------- ParamListener interface ----------------

  public void paramValueChanged( ParamField.Event e )
  {
    long  n    = (long) e.getTranslatedValue( ParamSpace.spcTimeSmps ).val;
    long  n2;
    Span  span;
 
    if( (e.getSource() == ggTimelineStart) || (e.getSource() == ggTimelineStop) ||
      (e.getSource() == ggTimelineLen) ) {

      span  = doc.timeline.getSelectionSpan();
     
      // ----- start was adjusted -----
      if( e.getSource() == ggTimelineStart ) {
        if( ggLockLen.isLocked() ) {
          n2  = n + span.getLength();
          if( n2 > doc.timeline.getLength() ) {
            n2  = doc.timeline.getLength();
            n  = n2 - span.getLength();
            ggTimelineStart.setValue( new Param( n, ParamSpace.spcTimeSmps.unit ));
          }
          span  = new Span( n, n2 );
          ggTimelineStop.setValue( new Param( n2, ParamSpace.spcTimeSmps.unit ));
        } else {
          n2 = span.getStop();
          if( n > n2 ) {
            n = n2;
            ggTimelineStart.setValue( new Param( n, ParamSpace.spcTimeSmps.unit ));
          }
          span  = new Span( n, n2 );
          ggTimelineLen.setValue( new Param( span.getLength(), ParamSpace.spcTimeSmps.unit ));
        }
      // ----- stop was adjusted -----
      } else if( e.getSource() == ggTimelineStop ) {
        if( ggLockLen.isLocked() ) {
          n2    = n - span.getLength();
          if( n2 < 0 ) {
            n2  = 0;
            n  = n2 + span.getLength();
            ggTimelineStop.setValue( new Param( n, ParamSpace.spcTimeSmps.unit ));
          }
          if( n > doc.timeline.getLength() ) {
            n  = doc.timeline.getLength();
            n2  = n - span.getLength();
            ggTimelineStop.setValue( new Param( n, ParamSpace.spcTimeSmps.unit ));
          }
          span  = new Span( n2, n );
          ggTimelineStart.setValue( new Param( n2, ParamSpace.spcTimeSmps.unit ));
        } else {
          n2    = span.getStart();
          if( n < n2 ) {
            n  = n2;
            ggTimelineStop.setValue( new Param( n, ParamSpace.spcTimeSmps.unit ));
          }
          if( n > doc.timeline.getLength() ) {
            n  = doc.timeline.getLength();
            ggTimelineStop.setValue( new Param( n, ParamSpace.spcTimeSmps.unit ));
          }
          span  = new Span( n2, n );
          ggTimelineLen.setValue( new Param( span.getLength(), ParamSpace.spcTimeSmps.unit ));
        }
      // ----- len was adjusted -----
      } else {
        if( ggLockStop.isLocked() ) {
          n2    = span.getStop() - n;
          if( n2 < 0 ) {
            n2  = 0;
            n  = span.getStop();
            ggTimelineLen.setValue( new Param( n, ParamSpace.spcTimeSmps.unit ));
          }
          span  = new Span( n2, n2 + n );
          ggTimelineStart.setValue( new Param( n2, ParamSpace.spcTimeSmps.unit ));
        } else {
          n2    = span.getStart() + n;
          if( n2 > doc.timeline.getLength() ) {
            n2  = doc.timeline.getLength();
            n  = n2 - span.getStart();
            ggTimelineLen.setValue( new Param( n, ParamSpace.spcTimeSmps.unit ));
          }
          span  = new Span( n2 - n, n2 );
          ggTimelineStop.setValue( new Param( n2, ParamSpace.spcTimeSmps.unit ));
        }
      }
      doc.timeline.editSelect( this, span );
    }
  }

  public void paramSpaceChanged( ParamField.Event e )
  {
    ggTimelineStart.setSpace( e.getSpace() );
    ggTimelineStop.setSpace( e.getSpace() );
    ggTimelineLen.setSpace( e.getSpace() );
  }
   
// ---------------- TimelineListener interface ----------------

  public void timelineSelected( TimelineEvent e )
    {
    if( e.getSource() != this ) {
      showTab( doc.timeline.getSelectionSpan().isEmpty() ? CURSOR_TAB : TIMELINE_TAB );
      updateTimeline();
    }
    }

  public void timelineChanged( TimelineEvent e )
  {
    updateTimeline();
  }
   
  public void timelinePositioned( TimelineEvent e ) { /* ignored */ }
    public void timelineScrolled( TimelineEvent e ) { /* ignored */ }

// ---------------- DocumentListener interface ----------------

  public void documentFocussed( de.sciss.app.DocumentEvent e )
  {
    if( doc != null ) {
      doc.timeline.removeTimelineListener( this );
      selColL[ 0 ].stopListening(); selColL[ 0 ] = null;
      selColL[ 1 ].stopListening(); selColL[ 1 ] = null;
      selColL[ 2 ].stopListening(); selColL[ 2 ] = null;
    }
    doc  = (Session) e.getDocument();
    for( int i = 0; i < 3; i++ ) ggTables[ i ].setDocument( doc ); // XXX dirty hack
    if( doc != null ) {
      doc.timeline.addTimelineListener( this );
      selColL[ 0 ] = new SessionCollectionListener( doc.getSelectedReceivers(), doc.getReceivers(), 1, ggNames[ 0 ], ggTables[ 0 ]);
      selColL[ 1 ] = new SessionCollectionListener( doc.getSelectedTransmitters(), doc.getTransmitters(), 2, ggNames[ 1 ], ggTables[ 1 ]);
      selColL[ 2 ] = new SessionCollectionListener( doc.getSelectedGroups(), doc.getGroups(), 2, ggNames[ 2 ], ggTables[ 2 ]);
      if( isListening ) {
        selColL[ 0 ].startListening();
        selColL[ 1 ].startListening();
        selColL[ 2 ].startListening();
      }
    }
    updateTimeline();
  }

  public void documentAdded( de.sciss.app.DocumentEvent e ) { /* ignore */ }
  public void documentRemoved( de.sciss.app.DocumentEvent e ) { /* ignore */ }

// ---------------- internal classes ----------------

  private class SessionCollectionListener
  implements ActionListener, SessionCollection.Listener, DynamicListening
  {
    private final int tab;
    private final SessionCollection scSel, scAll;
    private final JTextField ggName;
    private final SessionObjectTable ggTable;
 
    private SessionCollectionListener( SessionCollection scSel, SessionCollection scAll,
                       int tab, JTextField ggName,
                       SessionObjectTable ggTable )
    {
      this.scSel  = scSel;
      this.scAll  = scAll;
      this.tab  = tab;
      this.ggName  = ggName;
      this.ggTable= ggTable;
     
//      ggName.addActionListener( this );
//      new DynamicAncestorAdapter( this ).addTo( ancestor );
    }
 
    public void sessionCollectionChanged( SessionCollection.Event e )
    {
      showTab( scSel.isEmpty() ? CURSOR_TAB : tab );   // intelligently switch between the tabs
      ggTable.setObjects( scSel.getAll() );
      updateFields( e );
    }
   
    public void sessionObjectMapChanged( SessionCollection.Event e )
    {
      // nada
    }

    public void sessionObjectChanged( SessionCollection.Event e )
    {
      if( e.getModificationType() == SessionObject.OWNER_RENAMED ) {
        updateFields( e );
      }
    }

    // to be called with shared lock on 'doors'!
    private void updateFields( SessionCollection.Event e )
    {
      SessionObject          so;
      final int            numObj  = scSel.size();
      String              name;
      final de.sciss.app.Application  app    = AbstractApplication.getApplication();

      if( numObj >= 1 ) {
        so    = (SessionObject) scSel.get( 0 );
        name  = numObj == 1 ? so.getName() : app.getResourceString( "observerMultiSelection" );
      } else {
        name  = app.getResourceString( "observerNoSelection" );
      }
      ggName.setText( name );
    }

    public void actionPerformed( ActionEvent e )
    {
      SessionObject      so;
      int            i, num;
      AbstractCompoundEdit  edit;
      String          name;
      List          coll, coll2;
      Object[]        args;

      if( e.getSource() == ggName ) {
        coll  = scSel.getAll();
        coll2  = scAll.getAll();
        num    = coll.size();
        if( num == 0 ) return;
       
        args  = new Object[ 3 ];
       
        coll2.removeAll( coll );
        edit  = new BasicCompoundEdit();
        name  = ggName.getText();
        if( num == 1 ) {
          if( BasicSessionCollection.findByName( coll2, name ) != null ) {
            Session.makeNamePattern( name, args );
            name = BasicSessionCollection.createUniqueName( Session.SO_NAME_PTRN, args, coll2 );
          }
          edit.addPerform( new EditSetSessionObjectName( this, (SessionObject) coll.get( 0 ),
                                                         name ));
        } else {
          Session.makeNamePattern( name, args );
          for( i = 0; i < num; i++ ) {
            so    = (SessionObject) coll.get( i );
            name  = BasicSessionCollection.createUniqueName( Session.SO_NAME_PTRN, args, coll2 );
            edit.addPerform( new EditSetSessionObjectName( this, so, name ));
            coll2.add( so );
          }
        }
        edit.perform();
        edit.end();
        doc.getUndoManager().addEdit( edit );
      }
    }

    public void startListening()
    {
      scSel.addListener( this );
      ggName.addActionListener( this );
    }

    public void stopListening()
    {
      scSel.removeListener( this );
      ggName.removeActionListener( this );
    }
  } // class sessionCollectionListener
}
TOP

Related Classes of de.sciss.meloncillo.gui.ObserverPalette$SessionCollectionListener

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.