WordPerfect versus Word
A friend of mine just sent me the following news article, along with the subtitle, "Can we just let this die, geez..."
Novell Antitrust Lawsuit Against Microsoft Revived by Court
Bloomberg Businessweek - May 03, 2011
By Tom Schoenberg
Personally, I find articles like this depressing - not just because they are frivolous lawsuits that do little more than wasting millions of dollars for everyone concerned, but because they send the wrong messages to the business world. Let me explain:
I love quotes that are worded like this: "WordPerfect's share of the word-processing market fell to less than 10 percent in 1996 from almost 50 percent in 1990." This statement is an excerpt from a section in that article which suggests that Microsoft is the bad guy in this situation.
Has anyone ever bothered to consider that whatever happened to WordPerfect occurred because the executive leadership at WordPerfect made a plethora of poor business choices and their applications ceased to be good products? This entire lawsuit reminds me of when Metallica sued Napster over the decline in their album sales - did it ever occur to them (Metallica) that maybe they had passed their prime and perhaps no one wanted to buy their albums anymore?
Here's another question: did anyone else actually try to use WordPerfect for Windows 3.x through Windows 98? Well, I did - because back in my DOS days I was an avid WordPerfect 4.x through 6.x user. So take my word for it, every version of WordPerfect starting from 5.x through 8.x on Windows platforms were simply awful, while at the same time the versions of Word for Windows got better and better.
I can give you several reasons behind this dichotomy, but the primary cause is simple - WordPerfect didn't have a clue how to make a Windows-based product. As the world transitioned from a DOS-based environment to a Windows realm, WordPerfect shipped products that were technologically inferior, way behind schedule, and badly engineered. By the time that the folks at WordPerfect quit wasting money and figured out what they were doing, it was way too late - they owned less than 10% of the market, and the damage was irrevocable.
Here's just one example: instead of leveraging Windows' built-in printing capabilities and investing in better application features and functionality, the people at WordPerfect continued to develop and ship their own printing subsystem, which bypassed the built-in Windows printing features. Even if WordPerfect's alternate printing subsystem had been better, (which I can honestly say from personal experience that it was not), that's not the way that you're supposed to do things in a Windows world, and WordPerfect threw away millions of dollars and countless thousands of man hours on this colossal failure.
Here's another oldie but goodie - WordPerfect bought into the fantasy from the now-defunct Sun Microsystems that Java was the up-and-coming, be-all/end-all of computer languages and the dawn of write-once/run-everywhere software. This was a wonderful theory, and I personally spent some time writing simple applications in Java back in the mid-to-late-1990s because I, too, bought into Sun's hype. (I still wear a Java baseball cap that I got from Sun back in 1996.) But it wasn't long before I, like many others, realized that Java was mostly hype, and writing software in Java was an experience that was more like rewrite-often/debug-everywhere. But I realized my mistake before I had wasted over $400 million on a failed word processing application in Java like WordPerfect did.
But the folks at WordPerfect continued to press on in their self-delusions - all the while falling behind Word, which was now integrating wonderfully with Windows, Microsoft Office, and a host of other applications through technologies like DDE, OLE, and ActiveX. By this point WordPerfect's losses were enormous, then along came Novell, who was already a sinking ship; this was due to the fact that their difficult-to-use and expensive flagship NetWare operating system was taking a serious beating from Windows NT's ease-of-use and significantly reduced barrier-to-entry pricing.
Novell realized that WordPerfect had once been a major cash cow, and I guess they hoped that they could turn around both of these massive sinking ships and get them headed back from the Red Sea into the Black Sea. But Novell's delusions proved to be worse than WordPerfect's, and eventually Novell had to sell WordPerfect to Corel for a pittance just to keep their ship from being dragged under as WordPerfect rocketed toward the bottom in a technology fate that was worse than the demise of the Titanic. And yet, very much like the sinking of the Titanic and the untimely deaths of technology giants like Netscape and Sun Microsystems, WordPerfect's downfall was ultimately caused by a series of gargantuan blunders and the terminal hubris of their leadership, and not by any action on Microsoft's part.
Not that any of this will matter in court - Microsoft will probably still have to shell out a few hundred million dollars in "damages," thereby rewarding former executives at WordPerfect for their incompetence, and reinforcing the message to the business world that just because you're a colossal failure and you ruined the lives of thousands of your loyal employees, that doesn't mean that you shouldn't be able to buy a large mansion and luxury yacht by cashing in on the profits of your successful competitors.
Additional Reading
At the time of this writing, Wikipedia has a great write-up on the history of WordPerfect, including blunt analysis of WordPerfect's many failures. But pages on Wikipedia are subject to change, and they're not always accurate.
With that in mind, you might want to take a look at the book titled Almost Perfect by W. E. Peterson, who had been one of the senior executives at WordPerfect. Sometimes it's nice to have an insider's view of the breakdown and failure.
Outlook Macro: Export Appointments to TSV File
Using this Outlook VBA Macro
Over the years, I had noticed that I had appointments from years ago stuck in my calendar, so I wrote this Outlook VBA Macro to export a list of all my appointments to a tab-separated (TSV) file so that I could open it in Microsoft Excel and analyze all of my appointments. (After writing this macro, I wrote my Delete Old Appointments macro to delete old appointments.)
Outlook VBA Macro Example Code
Sub ExportAppointmentsToTsvFile()
Dim objOutlook As Outlook.Application
Dim objNamespace As Outlook.NameSpace
Dim objFolder As Outlook.MAPIFolder
Dim objAppointement As Outlook.AppointmentItem
Dim objNetwork As Object
Dim objFSO As Object
Dim objFile As Object
Dim strUserName As String
Set objOutlook = Application
Set objNamespace = objOutlook.GetNamespace("MAPI")
Set objFolder = objNamespace.GetDefaultFolder(olFolderCalendar)
Set objNetwork = CreateObject("WScript.Network")
strUserName = objNetwork.UserName
If InStr(strUserName, "\") = 0 Then
strUserName = objNetwork.UserDomain & "\" & strUserName
End If
Set objFSO = CreateObject("Scripting.FileSystemObject")
Set objFile = objFSO.CreateTextFile("c:\outlook-calendar.tsv")
objFile.WriteLine "UserName" & vbTab & _
"AppointementStart" & vbTab & _
"AppointementEnd" & vbTab & _
"AppointementRecurrenceState" & vbTab & _
"AppointementSubject" & vbTab & _
"AppointementSize" & vbTab & _
"AppointementUnRead" & vbTab & _
"AppointementLocation"
For Each objAppointement In objFolder.Items
DoEvents
objFile.WriteLine strUserName & vbTab & _
objAppointement.Start & vbTab & _
objAppointement.End & vbTab & _
objAppointement.RecurrenceState & vbTab & _
objAppointement.Subject & vbTab & _
objAppointement.Size & vbTab & _
objAppointement.UnRead & vbTab & _
objAppointement.Location
Next
MsgBox "Done!"
End Sub
Outlook Macro: Delete Old Appointments
Using this Outlook VBA Macro
Over the years, I had noticed that I had appointments from years ago stuck in my calendar, so I wrote this Outlook VBA Macro to help keep my outlook calendar thinned-out.
Note: This macros deletes appointments and attachments from your Outlook calendar - make sure that you want to do this before running this macro.
By default the macro will:
- Delete all appointments over a year old (except recurring appointments.)
- Delete all attachments from 6-month-old appointments.
- Delete large attachments from 2-month-old appointments.
You can alter these dates by adjusting the appropriate lines in the macro.
Outlook VBA Macro Example Code
Sub DeleteOldAppointments()
Dim objOutlook As Outlook.Application
Dim objNamespace As Outlook.NameSpace
Dim objFolder As Outlook.MAPIFolder
Dim objAppointement As Outlook.AppointmentItem
Dim objAttachment As Outlook.Attachment
Dim objNetwork As Object
Dim lngDeletedAppointements As Long
Dim lngCleanedAppointements As Long
Dim lngCleanedAttachments As Long
Dim blnRestart As Boolean
Dim intDateDiff As Integer
Set objOutlook = Application
Set objNamespace = objOutlook.GetNamespace("MAPI")
Set objFolder = objNamespace.GetDefaultFolder(olFolderCalendar)
Here:
blnRestart = False
For Each objAppointement In objFolder.Items
DoEvents
intDateDiff = DateDiff("d", objAppointement.Start, Now)
' Delete year-old appointments.
If intDateDiff > 365 And objAppointement.RecurrenceState = olApptNotRecurring Then
objAppointement.Delete
lngDeletedAppointements = lngDeletedAppointements + 1
blnRestart = True
' Delete attachments from 6-month-old appointments.
ElseIf intDateDiff > 180 And objAppointement.RecurrenceState = olApptNotRecurring Then
If objAppointement.Attachments.Count > 0 Then
While objAppointement.Attachments.Count > 0
objAppointement.Attachments.Remove 1 Wend
lngCleanedAppointements = lngCleanedAppointements + 1
End If
' Delete large attachments from 60-day-old appointments.
ElseIf intDateDiff > 60 Then
If objAppointement.Attachments.Count > 0 Then
For Each objAttachment In objAppointement.Attachments
If objAttachment.Size > 500000 Then
objAttachment.Delete
lngCleanedAttachments = lngCleanedAttachments + 1
End If
Next
End If
End If
Next
If blnRestart = True Then GoTo Here
MsgBox "Deleted " & lngDeletedAppointements & " appointment(s)." & vbCrLf & _
"Cleaned " & lngCleanedAppointements & " appointment(s)." & vbCrLf & _
"Deleted " & lngCleanedAttachments & " attachment(s)."
End Sub
Access Macro: Export Table/Query To Excel
Using this Access VBA Macro
I wrote this Access VBA Macro for a friend to export an Access table or query to a spreadsheet; it might come in handy. ;-]
Access VBA Macro Example Code
Sub ExportTableOrQueryToExcel()
Const strTitle = "This is my worksheet title"
Const strTableOrQuery = "Query1"
' define the path to the output file
Dim strPath As String
strPath = "C:\TestFile " & _
Year(Now) & Right("0" & _
Month(Now), 2) & Right("0" & _
Day(Now), 2) & ".xls"
' create and open an Excel workbook
Dim objXL As Object
Set objXL = CreateObject("Excel.Application")
objXL.WorkBooks.Add
objXL.Worksheets(1).Name = strTitle
objXL.Visible = False
' delete the extra worksheets
Dim intX As Integer
If objXL.Worksheets.Count > 1 Then
For intX = 2 To objXL.Worksheets.Count
objXL.Worksheets(2).Delete
Next
End If
' open the database
Dim objDB As DAO.Database
Dim objRS As DAO.Recordset
Dim objField As DAO.Field
Set objDB = CurrentDb
' open the query/table
Dim strSQL As String
strSQL = "SELECT * FROM [" & strTableOrQuery & "]"
Set objRS = objDB.OpenRecordset(strSQL)
Dim lngRow As Long
Dim lngCol As Long
If Not objRS.EOF Then
lngRow = 1: lngCol = 1
For Each objField In objRS.Fields
objXL.Worksheets(1).Cells(lngRow, lngCol).Value = objField.Name
lngCol = lngCol + 1
Next
lngRow = lngRow + 1
' loop through the table records
Do While Not objRS.EOF
lngCol = 1
For Each objField In objRS.Fields
objXL.Worksheets(1).Cells(lngRow, lngCol).Value = objField.Value
lngCol = lngCol + 1
Next
lngRow = lngRow + 1
objRS.MoveNext
Loop
End If
objXL.DisplayAlerts = False
objXL.ActiveWorkbook.SaveAs strPath, 46
objXL.ActiveWorkbook.Close
End Sub
FrontPage Visio Viewer Web Component
12/05/2010 UPDATE: The download link for the Visio viewer is no longer valid, and I'm sure that the GUID for any new viewer has changed. I'll fix this blog post when I have the chance to get all the new data together.
Summary
The Microsoft web site now offers a Visio Viewer Web Component for download. See the following URL for more information:
The purpose of this article is to show you how to use some of the FrontPage SDK functionality to add two new Web Components to FrontPage that will allow you to add the Visio Viewer to a web page.
NOTE - This example works in both FrontPage 2002 and FrontPage 2003.
More Information
STEP #1 - Locate your "Web Components" folder:
This will be in one of the following paths by default:
"C:\Program Files\Microsoft Office\OFFICE10\1033\WEBCOMP"
"C:\Program Files\Microsoft Office\OFFICE11\1033\WEBCOMP"
STEP #2 - Save the following INI file in the folder as "VISIO.INI":
[Component]
Name="Microsoft Visio"
Caption="C&hoose a component:"
Sorted=True
Type=Text
OnlyFor1033=True
[Component.Options]
Option1=Visio1
Option2=Visio2
[Visio1]
Name="Visio Viewer (With Wizard)"
Description="Insert a Visio Viewer component on your page."
URL="C:\Program Files\Microsoft Office\OFFICE11\1033\WEBCOMP\VISIO.HTM"
[Visio2]
Name="Visio Viewer (HTML Only)"
Description="Insert a Visio Viewer component on your page."
[Visio2.HTML]
HTML1=<object classid="clsid:279D6C9A-652E-4833-BEFC-312CA8887857"
HTML2=id="viewer1" width="300" height="300"
HTML3=codebase="http://download.microsoft.com/download/VisioStandard2002/vviewer/2002/W98NT42KMeXP/EN-US/vviewer.exe">
HTML4=<param name="BackColor" value="16777120">
HTML5=<param name="PageColor" value="16777215">
HTML6=<param name="GridVisible" value="1">
HTML7=<param name="PageVisible" value="1">
HTML8=<param name="HighQualityRender" value="1">
HTML9=<param name="ScrollbarsVisible" value="1">
HTML10=<param name="ToolbarVisible" value="1">
HTML11=<param name="AlertsEnabled" value="1">
HTML12=<param name="ContextMenuEnabled" value="1">
HTML13=<param name="PropertyDialogEnabled" value="1">
HTML14=<param name="SRC" value="">
HTML15=<param name="CurrentPageIndex" value="1">
HTML16=<param name="Zoom" value="-1">
HTML17=</object>
NOTE - You need to make sure that the URL parameter in the file matches your correct drive and Office version path.
STEP #3 - Save the following HTML file in the folder as "VISIO.HTM":
<html>
<head>
<meta name="GENERATOR" content="Microsoft FrontPage 6.0">
<meta name="ProgId" content="FrontPage.Editor.Document">
<meta http-equiv="Content-Type" content="text/html; charset=windows-1252">
<title>Visio Viewer</title>
<style>
.button { width=80px; }
.file { width=350px; }
.text { width=40px; }
body,td
{
font-family:'MS Sans Serif',verdana,arial;
font-size:9pt;
scrollbar-face-color:#cccccc;
scrollbar-base-color:#cccccc;
scrollbar-highlight-color:#cccccc;
scrollbar-shadow-color:#cccccc;
background-color:#cccccc;
color:#000000
}
</style>
<script language="JavaScript">
<!--
function insertHTML()
{
// build the HTML output
var html ='';
html += "<object classid=\"clsid:279D6C9A-652E-4833-BEFC-312CA8887857\" id=\"viewer1\" ";
html += " width=\"" + frmOptions.txtWidth.value + "\" ";
html += " height=\"" + frmOptions.txtHeight.value + "\" ";
html += " codebase=\"http://download.microsoft.com/download/VisioStandard2002/";
html += "vviewer/2002/W98NT42KMeXP/EN-US/vviewer.exe\">\n";
html += "<param name=\"BackColor\" value=\"16777120\">\n";
html += "<param name=\"PageColor\" value=\"16777215\">\n";
html += processCheckbox(frmOptions.chkGridVisible,"GridVisible");
html += processCheckbox(frmOptions.chkPageVisible,"PageVisible");
html += processCheckbox(frmOptions.chkHighQualityRender,"HighQualityRender");
html += processCheckbox(frmOptions.chkScrollbarsVisible,"ScrollbarsVisible");
html += processCheckbox(frmOptions.chkToolbarVisible,"ToolbarVisible");
html += processCheckbox(frmOptions.chkAlertsEnabled,"AlertsEnabled");
html += processCheckbox(frmOptions.chkContextMenuEnabled,"ContextMenuEnabled");
html += processCheckbox(frmOptions.chkPropertyDialogEnabled,"PropertyDialogEnabled");
html += "<param name=\"SRC\" value=\"" + frmOptions.txtVisioFile.value + "\">\n";
html += "<param name=\"CurrentPageIndex\" value=\"1\">\n";
html += "<param name=\"Zoom\" value=\"-1\">\n";
html += "</object>\n";
// preserve our options
setCookie("txtVisioFile",frmOptions.txtVisioFile.value);
setCookie("txtWidth",frmOptions.txtWidth.value);
setCookie("txtHeight",frmOptions.txtHeight.value);
setCookie("chkGridVisible",frmOptions.chkGridVisible.checked);
setCookie("chkPageVisible",frmOptions.chkPageVisible.checked);
setCookie("chkHighQualityRender",frmOptions.chkHighQualityRender.checked);
setCookie("chkScrollbarsVisible",frmOptions.chkScrollbarsVisible.checked);
setCookie("chkToolbarVisible",frmOptions.chkToolbarVisible.checked);
setCookie("chkAlertsEnabled",frmOptions.chkAlertsEnabled.checked);
setCookie("chkContextMenuEnabled",frmOptions.chkContextMenuEnabled.checked);
setCookie("chkPropertyDialogEnabled",frmOptions.chkPropertyDialogEnabled.checked);
// close the wizard
window.external.WebComponent.PreviewHTML = html
window.external.WebComponent.HTML = window.external.WebComponent.PreviewHTML;
window.external.WebComponent.Tag = "body";
window.external.Close(true);
}
function initializeForm()
{
frmOptions.txtVisioFile.value=getCookie("txtVisioFile","http://localhost/sample.vsd");
frmOptions.txtHeight.value=getCookie("txtHeight","300");
frmOptions.txtWidth.value=getCookie("txtWidth","300");
frmOptions.chkGridVisible.checked=((getCookie("chkGridVisible","true")=="true")?true:false);
frmOptions.chkPageVisible.checked=((getCookie("chkPageVisible","true")=="true")?true:false);
frmOptions.chkHighQualityRender.checked=((getCookie("chkHighQualityRender","true")=="true")?true:false);
frmOptions.chkScrollbarsVisible.checked=((getCookie("chkScrollbarsVisible","true")=="true")?true:false);
frmOptions.chkToolbarVisible.checked=((getCookie("chkToolbarVisible","true")=="true")?true:false);
frmOptions.chkAlertsEnabled.checked=((getCookie("chkAlertsEnabled","true")=="true")?true:false);
frmOptions.chkContextMenuEnabled.checked=((getCookie("chkContextMenuEnabled","true")=="true")?true:false);
frmOptions.chkPropertyDialogEnabled.checked=((getCookie("chkPropertyDialogEnabled","true")=="true")?true:false);
}
function processCheckbox(varBox,varName)
{
return("<param name=\""+varName+"\" value=\"" + ((varBox.checked == true) ? "1" : "0") + "\">\n");
}
function setCookie(strName, strValue)
{
document.cookie = strName + "=" + escape(strValue);
}
function getCookie(strName,strDefault)
{
var aryCookies = document.cookie.split("; ");
for (var i=0; i < aryCookies.length; i++)
{
var aryValues = aryCookies[i].split("=");
if (strName == aryValues[0])
{
var strValue = new String(aryValues[1]);
return ((strValue != 'undefined') ? unescape(strValue) : strDefault );
}
}
return strDefault;
}
-->
</script>
</head>
<body onload="initializeForm()">
<form name="frmOptions">
<table>
<tr>
<td colspan="5"><b>Display Options</b></td>
</tr>
<tr>
<td width="10"><input accesskey="d" type="checkbox" name="chkPageVisible" checked></td>
<td nowrap>Display the <u>d</u>rawing page</td>
<td width="20"> </td>
<td width="40"><input accesskey="h" type="text" class="text" name="txtHeight" value="300"></td>
<td nowrap><u>H</u>eight (in pixels)</td>
</tr>
<tr>
<td width="10"><input accesskey="g" type="checkbox" name="chkGridVisible" checked></td>
<td nowrap>Display the <u>g</u>rid if the drawing page is visible</td>
<td width="20"> </td>
<td width="40"><input accesskey="w" type="text" class="text" name="txtWidth" value="300"></td>
<td nowrap><u>W</u>idth (in pixels)</td>
</tr>
<tr>
<td width="10"><input accesskey="q" type="checkbox" name="chkHighQualityRender" checked></td>
<td colspan="4">Display using high-<u>q</u>uality rendering</td>
</tr>
<tr>
<td width="10"><input accesskey="t" type="checkbox" name="chkToolbarVisible" checked></td>
<td colspan="4">Display the <u>t</u>oolbar</td>
</tr>
<tr>
<td width="10"><input accesskey="s" type="checkbox" name="chkScrollbarsVisible" checked></td>
<td colspan="4">Display the <u>s</u>croll bars</td>
</tr>
</table>
<hr>
<table>
<tr>
<td colspan="2"><b>Event Processing Options</b></td>
</tr>
<tr>
<td width="10"><input accesskey="a" type="checkbox" name="chkAlertsEnabled" checked></td>
<td>Enable warning or <u>a</u>lert dialog boxes to show when an error occurs</td>
</tr>
<tr>
<td width="10"><input accesskey="c" type="checkbox" name="chkContextMenuEnabled" checked></td>
<td>Enable the <u>c</u>ontext menu to show on right-mouse events</td>
</tr>
<tr>
<td width="10"><input accesskey="p" type="checkbox" name="chkPropertyDialogEnabled" checked></td>
<td>Enable the <u>P</u>roperties and Settings dialog box to show on selection or toolbar events</td>
</tr>
</table>
<hr>
<table>
<tr>
<td nowrap>URL of <u>V</u>isio File</td>
<td><input class="file" accesskey="v" type="text" name="txtVisioFile"></td>
</tr>
</table>
<hr>
<table width="100%">
<tr>
<td align="right" nowrap>
<button class="button" accesskey="o" onclick="insertHTML();"><u>O</u>K</button>
<button class="button" accesskey="c" onclick="window.external.Close();"><u>C</u>ancel</button>
</td>
</tr>
</table>
</form>
</body>
</html>
STEP #4 - Open a new page in FrontPage
STEP #5 - Click "Insert" -> "Web Component"
STEP #6 - Select "Microsoft Visio" in the list of component types
STEP #7 - Choose to insert the HTML-only version or the the wizard-based version
NOTES:
- The HTML-only version inserts just the ActiveX control, allowing you to modify the raw HTML, but is less user-friendly when you double-click it.
- The wizard-based version is more user-friendly for inserting and modiyfing the control, but it is a web-bot and therefore does not allow you to modify the raw HTML.
- The codbase download path for the control is hard-coded; if that changes, you will need to update the INI file and HTML file accordingly.