free script: interactive region list

nolty wrote on 11/17/2004, 1:53 AM
/**
***************************************************************************
* RegionList.js for Sony Vegas 5 *
* A script by Robert Nolty, www.otpproductions.com *
* *
* You may use, modify or distribute this script freely. *
* If you would like to hire me to adapt it or do other Vegas scripting, *
* contact me through my website. *
* 16 Nov 04 *
***************************************************************************
*
* Functionality:
* View a list of all regions
* Click column headings to sort on that column
* Double click a region to select it in the timeline
* Click within a selected name to change the name of the region
* Right-click a region and select "Delete" from pop-up menu to delete a region
* Select a region and hit the "Del" key to delete a region
*
* You may move back and forth between the Region List window and Vegas without closing the Region List.
* If you make changes to the regions in Vegas, hit the Refresh button in the Region List window.
* The Region List window will remain visible on top of the Vegas window as long as the On Top box is checked.
*
* Bugs/issues:
* Requires Vegas 5 (could probably be adapted to Vegas 4 trivially -- search for "Vegas 5" below).
* Scrolling Vegas display to selected region will break when Vegas 6 is released.
* Delete item appears on context menu even if no item is selected for deletion.
* RETURN and ESCAPE keys do not work for dismissing the form.
* Multiple instances of the Region List may be opened, from within one instance of Vegas, or from
* multiple instances of Vegas. After selecting a region in a Region List, the wrong Region List
* may receive focus.
* If multiple instances of Vegas are open to the same project, after selecting a region the wrong
* instance of Vegas may be scrolled to the selected region.
* Cannot undo changes made by Region List; and Vegas does not realize project is modified. (NOTE: if
* you need undo functionality, change theForm.Show to theForm.ShowDialog. Then you will not be able
* to work in the Vegas window until the Region List is closed, but you will get undo and Vegas will
* mark the project as modified if you modify a region name or delete a region in the dialog.)
**/

import System.Text;
import System.Collections;
import System.Windows.Forms;
import System.Globalization;
import Sony.Vegas;

import Microsoft.Win32; // for WSH scripting

try {
var theForm = new RegionListForm(); // RegionListForm defined below
theForm.Show(); // Make it non-modal, so user can select a region and keep RegionList open
} catch (e) {
if (!e.skipMessageBox)
MessageBox.Show(e);
}

class RegionListView extends ListView {
// weird -- when form is non-modal (i.e. when I open it with theForm.Show, rather than theForm.ShowDialog)
// then Vegas.Project exists during initialization of the form; but after that if the user interacts in
// a way that calls an event handler, referring to Vegas.Project generates a runtime error, "Object reference
// not set to an instance of an object". When form is modal I don't have the problem. Since I like the
// non-modal functionality, I work around the problem by storing the Vegas.Project in a variable during
// initialization; then I can use the stored variable in my event handlers.
var m_Project : Sony.Vegas.Project;

var m_mnuDelete : MenuItem;
var m_mnuContext : System.Windows.Forms.ContextMenu;

function RegionListView() { // constructor
this.Size = new System.Drawing.Size(400, 400);
// by anchoring to all 4 sides, ListView resizes when its parent form resizes
this.Anchor = AnchorStyles.Left + AnchorStyles.Right + AnchorStyles.Top + AnchorStyles.Bottom;
this.Columns.Add("Region Name",200,HorizontalAlignment.Left);
this.Columns.Add("Start Time",-2,HorizontalAlignment.Right);
this.Columns.Add("End Time",-2,HorizontalAlignment.Right);
this.FullRowSelect = true; // clicking anywhere in row selects row
this.AllowColumnReorder = true;
this.LabelEdit = true; // users can rename regions in the RegionList control (requires event handler)
this.Sorting = SortOrder.None; // will be changed if user clicks a column heading
this.View = System.Windows.Forms.View.Details;
this.m_Project = Vegas.Project;

// register the event handlers
this.add_DoubleClick(RegionListDoubleClickHandler);
this.add_AfterLabelEdit(RegionListAfterLabelEditHandler);
this.add_ColumnClick(RegionListColumnClickHandler);
this.add_KeyUp(RegionListKeyUpHandler)

// make the context menu
m_mnuDelete = new MenuItem();
m_mnuDelete.Text = "&Delete";
m_mnuDelete.add_Click(RegionListCMenuDeleteHandler);
m_mnuContext = new System.Windows.Forms.ContextMenu();
m_mnuContext.MenuItems.Add(m_mnuDelete);
this.ContextMenu = m_mnuContext;

this.Populate(); // gets list of regions from Vegas and puts in ListView as Items
} // end of constructor

public function Populate() {
// charge through the Vegas project regions, adding them to ListView as Items
for (var region : Sony.Vegas.Region in this.m_Project.Regions) {
var newItem : ListViewItem = new ListViewItem(region.Label);
newItem.SubItems.Add(TimeToString(region.Position));
newItem.SubItems.Add(TimeToString(region.End));
// setting the Tag attribute attaches this region variable to this row in the ListView
newItem.Tag = region;
this.Items.Add(newItem);
}
}

protected function deleteSelectedRegion() {
// called by 2 handlers -- the contextMenu delete item, and the Delete key KeyPress
if (this.SelectedItems.Count == 1) { // if nothing selected, do nothing
// remove the corresponding region from Vegas
var region : Sony.Vegas.Region = this.SelectedItems[0].Tag;
m_Project.Regions.Remove(region);
// remove the item from the ListView
this.Items.Remove(this.SelectedItems[0]);
}
}

protected function RegionListCMenuDeleteHandler(o : Object, e : System.EventArgs) {
// called when Delete selected from Context Menu
this.deleteSelectedRegion();
}

protected function RegionListKeyUpHandler(o : Object, e : KeyEventArgs) {
// I wanted to use the KeyPress event, but the Del key doesn't appear to raise that event
// respond only to Delete key
if (e.KeyCode == Keys.Delete) {
this.deleteSelectedRegion();
}
}

protected function RegionListDoubleClickHandler(o : Object, e : System.EventArgs) {
// Select the region in timeline
var region : Sony.Vegas.Region = this.SelectedItems[0].Tag;
Vegas.Cursor = region.Position;
Vegas.SelectionStart = region.Position;
Vegas.SelectionLength = region.Length;

// Vegas interface does not allow to scroll the timeline display. Activate the Vegas
// app and send the Up-arrow and Down-arrow keystrokes to ensure cursor is visible.
// Figure out the appName -- in my version of Vegas, it is "project.veg - Sony Vegas 5.0" or
// "project.veg * - Sony Vegas 5.0" (if project has been modified)
var projectName : String = this.m_Project.FilePath;
projectName = projectName.substring(projectName.lastIndexOf("\\") + 1);
var appName : String = projectName + " - Sony Vegas 5.0";

var wshShell = new ActiveXObject("WScript.Shell");
var activateWasSuccessful : Boolean = wshShell.AppActivate(appName);
if (! activateWasSuccessful) {
appName = projectName + " * - Sony Vegas 5.0";
wshShell.AppActivate(appName);
}
System.Threading.Thread.Sleep(100); // puts current thread to sleep for 100 milliseconds
SendKeys.SendWait("{Up}");
SendKeys.SendWait("{Down}");
wshShell.AppActivate(this.FindForm().Text);
}

protected function RegionListAfterLabelEditHandler(o:Object,e:LabelEditEventArgs) {
// Take the text just entered by user and make it the name of the selected region
var region : Sony.Vegas.Region = this.SelectedItems[0].Tag;
region.Label = e.Label;
}

protected function RegionListColumnClickHandler(o:Object,e:ColumnClickEventArgs) {
// sort along clicked column
// just register a new item comparer, then sort happens automatically
// first, check if this was already the column of sort; in that case reverse the sort
if (this.ListViewItemSorter instanceof RegionListItemComparer &&
(RegionListItemComparer)(this.ListViewItemSorter).m_sortColumn == e.Column) {
if (this.Sorting == SortOrder.Ascending) {this.Sorting = SortOrder.Descending;}
else {this.Sorting = SortOrder.Ascending;}
}
else {
// first time a column is clicked, or a new column has been clicked
this.Sorting = SortOrder.Ascending;
}
this.ListViewItemSorter = new RegionListItemComparer(e.Column,this.Sorting); // defined below
}
}

class RegionListItemComparer implements IComparer {
var m_sortColumn : int;
var m_sortOrder : SortOrder;

function RegionListItemComparer(iCol : int, so : SortOrder) { // constructor
m_sortColumn = iCol;
m_sortOrder = so;
}

// to implment IComparer interface, must provide a Compare function
public function Compare(x : Object, y : Object) : int {
// Implements IComparer.Compare
// at run time, x and y will be of type ListViewItem, and so have SubItems member
if (m_sortOrder == SortOrder.Ascending) {
return String.Compare(x.SubItems[m_sortColumn].Text,y.SubItems[m_sortColumn].Text);
}
else {
return -1*String.Compare(x.SubItems[m_sortColumn].Text,y.SubItems[m_sortColumn].Text);
}
}
}

class RegionListForm extends Form {
// save the controls in the form as members of the class -- I don't actually use these
// variables for anything, but they're there if I need them....
var m_lblForm : Label;
var m_pnlOnTop : Panel;
var m_ckbxOnTop : CheckBox;
var m_lblOnTop : Label;
var m_listView : RegionListView;
var m_btnRefresh : Button;
var m_btnDismiss : Button;

function RegionListForm() { // constructor
this.Text = "Region List";
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.Sizable;
this.StartPosition = FormStartPosition.CenterScreen;
this.Width = 440;
this.Height = 510;

m_lblForm = new Label();
m_lblForm.Text = "A free script from www.otpproductions.com";
// make the label in italics, and a bit larger than default font
m_lblForm.Font = new System.Drawing.Font(m_lblForm.Font.FontFamily, 1.25*m_lblForm.Font.Size);
var newFontStyle : System.Drawing.FontStyle = m_lblForm.Font.Style;
newFontStyle |= System.Drawing.FontStyle.Italic;
m_lblForm.Font = new System.Drawing.Font(m_lblForm.Font,newFontStyle);
m_lblForm.Location = new System.Drawing.Point(10,10);
m_lblForm.Size = new System.Drawing.Size(m_lblForm.PreferredWidth+5, m_lblForm.PreferredHeight);
m_lblForm.BorderStyle = BorderStyle.FixedSingle;

m_ckbxOnTop = new CheckBox();
m_ckbxOnTop.Size = new System.Drawing.Size(10,15);
m_ckbxOnTop.Location = new System.Drawing.Point(2,4); // relative to panel it will go into
m_ckbxOnTop.Checked = true;
m_ckbxOnTop.add_Click(ckbxOnTopClickHandler);
this.TopMost = true; // TopMost will be toggled whenever user toggles checkbox (requires handler)

m_lblOnTop = new Label();
m_lblOnTop.Text = "On Top";
m_lblOnTop.Size = new System.Drawing.Size(m_lblOnTop.PreferredWidth+5, m_lblOnTop.PreferredHeight);
m_lblOnTop.Location = new System.Drawing.Point(m_ckbxOnTop.Right+10,4);

m_pnlOnTop = new Panel();
m_pnlOnTop.Size = new System.Drawing.Size(m_ckbxOnTop.Size.Width+m_lblOnTop.Size.Width+10,
m_lblOnTop.Size.Height+10);
m_pnlOnTop.BorderStyle = BorderStyle.FixedSingle;
m_pnlOnTop.Anchor = AnchorStyles.Right + AnchorStyles.Top;
m_pnlOnTop.Controls.Add(m_ckbxOnTop);
m_pnlOnTop.Controls.Add(m_lblOnTop);
// Delay adding until we can compute location relative to ListView control

m_listView = new RegionListView();
m_listView.Location = new System.Drawing.Point(10,m_lblForm.Bottom+10);

// Make pnlOnTop flush with listView on the right
m_pnlOnTop.Location = new System.Drawing.Point(m_listView.Right - m_pnlOnTop.Size.Width,10);

m_btnRefresh = new Button();
m_btnRefresh.Text = "Refresh";
m_btnRefresh.Location = new System.Drawing.Point(130,m_listView.Bottom+10);
m_btnRefresh.Anchor = AnchorStyles.Bottom;
m_btnRefresh.add_Click(btnRefreshClickHandler);

m_btnDismiss = new Button();
m_btnDismiss.Text = "Dismiss";
m_btnDismiss.Location = new System.Drawing.Point(210,m_listView.Bottom+10);
m_btnDismiss.Anchor = AnchorStyles.Bottom;
m_btnDismiss.add_Click(btnDismissClickHandler);

this.Controls.Add(m_pnlOnTop);
this.Controls.Add(m_lblForm);
this.Controls.Add(m_listView);
this.Controls.Add(m_btnRefresh);
this.Controls.Add(m_btnDismiss);

// following two lines are ineffectual, I don't know why
this.AcceptButton = m_btnDismiss; // hitting RETURN is same as pressing Dismiss button
this.CancelButton = m_btnDismiss; // hitting ESC is same as pressing Dismiss button
this.ActiveControl = m_btnDismiss;
} // end of RegionListForm constructor

protected function btnRefreshClickHandler(o: Object, e: System.EventArgs) {
m_listView.Items.Clear();
m_listView.Populate();
}

protected function btnDismissClickHandler(o: Object, e: System.EventArgs) {
this.Close();
}

protected function ckbxOnTopClickHandler(o: Object, e: System.EventArgs) {
// toggle TopMost property of form (checked state of check box toggles automatically)
this.TopMost = ! this.TopMost;
}
}

function TimeToString(time) : String
{
// from System.Globalization import. Copied from Sony's "Export Regions as Subtitles" script.
var decimalSeparator = CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator;

var rgTime = time.ToString( RulerFormat.Time ).split(decimalSeparator); // {"hh:mm:ss", "ddd"}
var sbRes : StringBuilder = new StringBuilder();

sbRes.Append( rgTime[0]);
sbRes.Append( ':' );

var iCentiseconds = Math.round( rgTime[1]/ 10);

sbRes.Append((( iCentiseconds / 10 ) >> 0 )% 10 );
sbRes.Append((( iCentiseconds / 1 ) >> 0 )% 10 );

return sbRes;
}

Comments

jetdv wrote on 11/17/2004, 7:11 AM
If you use the < PRE > and < /PRE > HTML codes around your code, the original formatting will be maintained.
johnmeyer wrote on 11/17/2004, 8:15 AM
While the script itself is only marginally useful (since you can do most of this from the Edit Detail window, the concepts and functionality demonstrated by this script are extremely important. Perhaps others already knew how to write a script that let you access some of the Vegas interface while the script was still running. I certainly didn't know how to do that.

Nice work.
BrianStanding wrote on 11/17/2004, 8:58 AM
I've been asking about this kind of interactive Edit Details window interface for some time. I look forward to trying it out.

Thanks!
rcampbel wrote on 11/17/2004, 1:27 PM
Vegas 5 does allow you some ability to have interactive scripts as shown in this example (this approach will not work in Vegas 4). However, there are some things to watcht out for. The biggest one is that you lose undo. Vegas creates an undo point for a script based upon the changes between when the script starts and when it exits. Since the interactive script exits before it does anything, any changes to the project after the script exits will not have an undo point.

Randall