mirrorMan - 自分のフォルダコピーなんて

コピーしたフォルダを自分自身にペーストしようとしたら、
普通なら↓のような画面をExplorerが出してくれるが、


マウスではじくとまれに自分のフォルダの中に自分をコピーしてしまうことがあった。
一瞬の動作すぎるのか、Shell.Applicationがなんか追い越してしまうのか?
if(strTgtFol == toHereItm.Path){
wsh.popup("送り側と受け側のフォルダが同じです。\n\n"+strTgtFol,0,document.title,64);
return setTimeout('loadItms(1)',0);
}






mirrorMan - 名前変更時チェック追加

ファイルやフォルダの名前変更時に使用できない文字のチェックをしていなかったので追加
var elms = document.getElementsByTagName('INPUT');
for(var i in elms){
var itmNam = elms[i].value;
if(/[\\\/:\*\?"<>\|]/.test(itmNam)){
return wsh.popup("mirrorManではファイル名、フォルダ名に"+
"次の半角文字は入力できません。\n\n "+
"\\ / : * ? \" <> | ",0,document.title,64);
}
ssh.NameSpace(getFol(getAdrBar(LR))).ParseName(beforeNm).Name = itmNam;
elms[i].parentNode.innerText = itmNam;
}

二重引用符は偶数だと使えるし表示もされるが奇数だとだめ。




mirrorMan - ウインドウリサイズ

Googleブログだと明日ダウンロード開始との新しいブラウザ、 Chrome わくわく。HTAみたなことできないかな、できないだろうな、できないにきまってるわな。


mirrorMan.htaは、画面幅もリサイズするように変更しました。
HTMLがレイアウトされた後に各要素のoffsetWidthを足し合わせてリサイズ画面幅を求めます。

VistaでAero機能をオンにするとウインドウ枠の幅がいちじるしく変わってくるので、navigator.userAgentで足す値を分けたり、
var osflame = /Windows NT 6/.test(navigator.userAgent) ? [32, 95] : [22, 91];
window.resizeTo()やwindow.moveTo()はマウス押しっぱだとエラーになるので、リトライ処理も律儀に。
try{
try{ resizeTo(w,h) }
catch(e){
if(winCnt == maxCnt) throw e;
else if(winCnt < maxCnt) throw ++winCnt;
}
}catch(e){
if(typeof(e) =="object") downWin("リサイズ",e.description);
else if(ee < maxCnt){
resizeCnt.innerText = ++winCnt;
setTimeout('winPositionize()',100);
}
}
カウンタ変数のwinCntがリトライ上限の20になるまでインクリメントして、エラーオブジェクトがスローされたらdownWin()を読んでポップアップ表示後、強制的に画面を閉じてさようならします。
function downWin(mod, desc){
wsh.popup("ウインドウムーブ時に"+ e.description +"エラーによるリトライ回数が "+
winCnt+" 回に達したため、"+document.title+"を終了します", 0, document.title, 16);
return closeWin();
}





mirrorMan - オンマウスでフェードイン

いつもチェックさせてもらっているcolissさんのサイトで、おもしろいエフェクトを紹介されていたのでmirrorManにもつけてみました。
お気に入りボタンにフェードインのエフェクトをつけたソースの抜粋です。
<style>
.setLstSpn {display:block;padding:0px 8px 0px 8px;
background:url(img/setLstLft.gif) no-repeat -1px right
}
</style>
<script>
with(document.body){
onmouseover = handleMouseOver;
}
function handleMouseOver(){
var src = event.toElement||event.srcElement;
var cls = src.className;

…(省略)

}else if(/^setLstSpn/.test(cls)){
$(src).toggle().fadeIn();
if(/MSIE 6/.test(navigator.appVersion)) $(src).css('display','inline');

src.style.background = 'url(img/setLStRht.gif) no-repeat -1px right';
}
}
</script>
ここでfadeIn()は思いつかなかった!

フォーカスが
フェードインで
明るくなります

追記:PCのIEが6だった場合に要素がブロック化してレイアウトがぐちゃぐちゃになってしまっていたので、displayプロパティをinlineに設定しなおす行を追加しました。





mirrorMan - ドライブ割当て

Windowsサーバはドライブを割り当てるほうがアクセスが速いし安定している、気がする。
のでドライブを割り当てています。

画面上では\\(バックスラッシュ)で始まるUNCパスで表示していますが、


ドライブも割り当てています。


内部で処理するのに気を付けた点は、
* ユーザはドライブ文字を意識しない。
* 親ディレクトリで割り当てられたドライブがあればそれを使う。
* 終了時に一括して切断する。

1つめは、空いているドライブ文字を検索してから割り当てます。
2つめは、割り当てパスとの前方一致でチェックします。
3つめは、mirrorMan自身で割り当てたドライブを繰り返し処理で切断します。

 1|function chkMapping(path){
2| if(!ssh.NameSpace(path)) return path;
3| if(!sfs.GetDriveName(path)) return path;
4| if(!sfs.GetDrive(sfs.GetDriveName(path)).DriveLetter){
5| var enumDrvs = (new ActiveXObject("WScript.Network")).EnumNetworkDrives();
6| for(var i=0; i<enumDrvs.length; i+=2){
7| if(!enumDrvs.Item(i)) continue;
8| if(enumDrvs.Item(i+1).toLowerCase() == path.toLowerCase())
9| return enumDrvs.Item(i)+"\\";
10| if(0==path.toLowerCase().indexOf((enumDrvs.Item(i+1)+"\\").toLowerCase())){
11| var lst = path.slice(enumDrvs.Item(i+1).length);
12| var pth = sfs.BuildPath(enumDrvs.Item(i), lst)
13| return pth;
14| }
15| }
16| for(var i=71; i<92; i++){
17| if(i == 91) return wsh.popup("ドライブが全て使用されています");
18| var d = String.fromCharCode(i);
19| if(!sfs.DriveExists(sfs.GetDriveName(d+":"))){
20| if(mapDrive(d, path)) return d+":\\";
21| else return false;
22| }
23| }
24| }else return path;
25|}
2行目はパスがNameSpace()が通るかどうかチェックしています。
3行目はドライブ文字を取得するFileSystemObjectのメソッドですが、UNCパスでもtrueが返ってきます。逆に特殊フォルダではfalseになるのでチェックに使用しています。
4行目で、ドライブ文字からドライブオブジェクトを取得できなければ、ドライブを割り当てるべきパスとしています。
5行目はドライブ文字のコレクションを取得しています。
6行目ですが、このコレクションはドライブ文字とパスのペアになっているのでi+=2で回します。
8行目では、既に割り当てたドライブがあればそのドライブを返しています。
11行目からは、親フォルダにドライブ割り当てされたパスが含まれていれば、それ以降のパスをくっつけて返しています。たとえば、\\fogeというパスがF:ドライブに割当たっているとして、\\foge\foo\だったらF:\fooというパスにして返しています。
16行目からは空いているドライブ文字が見つかるまで検索し、mapDrive()関数を呼び出しています。
 1|function mapDrive(d, path){
2| try{(new ActiveXObject("WScript.Network")).MapNetworkDrive(d+":",path,false)}
3| catch(e){return wsh.popup(e.number+":"+e.description,0,document.title,16)}
4| mappedDrv.pushDrv(d);
5| return true;
6|}
ドライブを割り当てています。割り当て後、ドライブ文字を次のオブジェクトに追加しています。
var ArrMapDrvs = function(d){
this.drvs = [];
this.pushDrv = function(d){ this.drvs.push(d) }
}
var mappedDrv = new ArrMapDrvs();
ドライブ文字を格納する配列とその配列への追加メソッドを持つオブジェクトです。
最後は、終了時に呼び出される切断処理です。
document.body.onunload = closeWin;
function closeWin(){
for(var i in mappedDrv.drvs) if(! unmapDrive(mappedDrv.drvs[i]+":")) return;
window.close();
}
ドライブ毎にunmapDrive()を呼び出してます。
 1|function unmapDrive(d){
2| try{ (new ActiveXObject("WScript.Network")).RemoveNetworkDrive(d) }
3| catch(e){
4| var sug = e.number =="-2147022492" || e.number =="-2147022495"
5| ? "切断してからmirrorManを終了するには、"+
6| d+"ドライブを使用中のファイル及びフォルダを閉じて再試行ボタンを押します。"
7| : "\n" ;
8| var btn = wsh.popup(d +" ドライブが切断できませんでした。\n\n"+
9| e.description+"\n"+ sug , 0, document.title, 69);
10| switch(btn){
11| case 3: return false;//[中止]
12| case 4: unmapDrive(d);//[再試行]
13| }
14| }
15| return true;
16|}
エラー番号はXPとVista、もしくはIE6とIE7or8で異なっています。
実際に表示されるメッセージは次のようになります。







mirrorMan - フォーカスローテート2

2はソースです。
function moveSelItm(){
var eID, iID = cntSelItmId(), iLR = getLR(2);
if((event.keyCode==37)||(event.keyCode==39)){
if(/^txt0|^txt2/.test(event.srcElement.className)) return;
if(/itmRnmFrm/.test(event.srcElement.id)) return;
}
setEventRetFalse();
switch(event.keyCode){
case 37://←
case 39://→
iLR = iLR ? iLR = 0 : iLR = 2;
eID = getLR(1) + iID; break;
case 38://↑
if(iID) eID = getLR(0) + --iID;
else if(!iLR){
eID = getLR(0) + lftPains.Count;
iID = lftPains.Count;
}else{
eID = getLR(0) + rhtPains.Count;
iID = rhtPains.Count;
} break;
case 40://↓
if(iID) eID = getLR(0) + ++iID;
else{
eID = getLR(0) + ++iID;
if(!document.all(eID)) return;
}
}
if(document.all(eID)){
cntSelItmId(iID);
selItmLR.innerText = iLR;
if(document.all(eID).firstChild.id =="itmRnmFrm")
txtBoxFocus(document.all(eID).firstChild);
else{
selItm(document.all(eID));
keyDowns.innerText ="";
}
}else if(iID > 0){
switch(event.keyCode){
case 37://←
case 39://→
cntSelItmId(iID -1); break;
case 40://↓
cntSelItmId(0);
}
return moveSelItm();
}
}

方向キーが押されるたびに呼び出されるfunctionです。
cntSelItmId()は、フォーカスのあるアイテムのインデックス番号を返します。番号は上から順に0,1,2です。
押されたキーが←か→であれば、テキストボックスが表示中かどうかをチェックして処理を行わずに返します。
setEventRetFalse()はevent.returnValue = false;をしています。たとえばウインドウにスクロールバーが表示されてい場合に、IEの方向キーでの画面スクロール動作が同時に行われてしまうのをオフにしています。
その次のswitch文はフォーカス移動先のインデックス番号を求める処理です。方向キーでは一度に一つしかフォーカスは移動しないので、インクリメントかデクリメントのみですね。
lftPains、rhtPainsの自前オブジェクトは、画面更新時にそれぞれのペインのファイル/フォルダのオブジェクトを格納していて、それらの数のCountプロパティも追加しています。このCountプロパティ値で、左右ペインを移動したときに一番下のファイルにフォーカスを移るようにしています。

そして、求めたeIDは見てのとおりタグのid属性値というわけです。その要素が存在すればそちらへ、なければ再帰的に呼びなおすことでフォーカスローテートさせています。最初に考えていたよりコンパクトにできたので、これもお気に入りロジックです。








mirrorMan - フォーカスローテート

IEはブラウザです。フォーカスはフォーム部品のみで、動かすのもTABキーです。
しかしフォーカスを方向キーで動かせないとファイラとはいえません。

フォーカスは1_名無しフォルダにあり、1_名無し.txtは名前変更中です。
この時点でエクスプローラではあり得ない状態です。エクスプローラでは名前変更中に↑↓キーを押してもカーソルが移動するだけですが、mirrorManではフォーカスが移るようにしています。名前変更中に他のファイルやフォルダの名前をコピーしたいときと、↑↓キーでカーソルを動かそうとするときとで、どちらがよくありますか?



↓キーを押して、ファイル名の文字を全選択した状態にフォーカスが移りました。









ここで、←→キーを押すとカーソルが移動します。










SHIFT+←→キーで部分選択して、Ctrl+CしたりCtrl+Vしたり。

さらに↓キーを押すと、次の画像のように、フォーカスが下に移ります。







ここから→キーを押してみます。



右ペインの一番下のファイルにフォーカスが移動しました。
ここでCtrl+Cを押してコピーし、
→キーか、←キーのどちらかを押してみます。



←キーを押せば左ペインに移り、右ペインで→キーを押しても、ローテーションして左ペインに移ります。一番下で↓キー、一番上で↑キーでも同様にローテートします。
↓キーで名前変更状態のファイルに移ります。

さらにShift+←→キーで「名無し.txt」を範囲選択し、先ほどコピーしたファイル名をCtrl+Vで貼り付けます。









選択範囲に貼り付けられました。











名前変更を行うにはエクスプローラと同様にF2キーか、右クリックメニューから名前変更を選択します。名前変更状態でない場合にCtrl+CやCtrl+Vを押した場合は、エクスプローラと同様にコピーorペーストするので、mirrorManでコピーしたファイルをデスクトップやエクスプローラに貼り付けたり、逆にエクスプローラでコピーしたファイルをmirrorManに貼り付けたりできます。
ただ、ドラッグドロップで他のウインドウへ運ぶのは未実装で、IEの機能のままです。たとえばエクセル画面にドラッグドロップしたりすると、HTMLで貼り付けられちゃったりしてしまいます。




mirrorMan - ZIP圧縮解凍












右クリックメニューからZIP圧縮を選択します。















緑色でmirrorMan.zip圧縮フォルダが作成されました。非同期処理なので、表示されない場合は画面を更新してください。



中を開いて参照することも可能です。もう一方のペインから圧縮フォルダ内に追加コピーすることもできますが、非可視の怪しげなシステムファイルができるケースもあり、個人情報を含めずに人に渡したい場合は、しない方がいいかもしれません。











続いてソースの抜粋です。

<script type="text/javascript" defer src="./js/jquery.contextmenu.r2.js"></script>
コンテキストメニューのjQueryプラグインを読み込みます。

<div class="contextMenu" id="conMenuLftItms">
<ul>
<li id="verbZipTo">ZIP圧縮 </li>
<li id="verbZipfrom">ZIP解凍 </li>
</ul>
</div>
コンテキストメニューに表示する要素を作成します。

function initContextMenu(){
$('#rhtClkLftItmsAra').contextMenu('conMenuLftItms',{
bindings:{
'verbZipTo' : function(t){ invokeVerbZip() },
'verbZipfrom': function(t){ invokeVerbZip('extract') }
}
}
選択された時の動作を起動時に読み込ませます。

function invokeVerbZip(p){
if(!selItmObj) return;
var intLR = getLR(4);
if(p =="extract")
ssh.NameSpace(getAdrBar(intLR)).CopyHere(selItmObj.GetFolder.Items());
else{
cntSelItmId(0);
selItmObj.InvokeVerbEx("copy");
var zt = ["Compressed (zipped) Folder.ZFSendToTarget", //XP
"圧縮 (zip 形式) フォルダ.ZFSendToTarget"]; //Vista
for(var i in zt){
var tgt = ssh.NameSpace(wsh.SpecialFolders("SendTo")).ParseName(zt[i]);
if(tgt){ tgt.InvokeVerbEx("paste");break}
}
}
}
selItmObjは現在フォーカスされているファイル/フォルダ名からShell.ApplicationでNameSpace(fol).ParseName(item)したものを格納しています。フォーカスが外れるとnullにしているので、右クリック時に選択されているファイル/フォルダがあるかどうかのチェックにしています。
解凍が選択された場合、selItmObjのフォルダオブジェクトのItems()をCopyHereしてやると解凍されます。

そういうことのようです。
解凍さえしてくれれば僕は満足です。

圧縮が選択された場合、エクスプローラの右クリックメニューをシミュレートしてくれるInvokeVerbEx("copy")メソッドを使用してselItmObjをクリップボードに送ります。これでエクスプローラ上でファイル/フォルダをコピーしたのと同等の状態になります。
そして、同じくエクスプローラの右クリックメニュー「送る」の特殊フォルダにある特殊ファイルに対し、また同じく、エクスプローラの右クリックメニュー「貼り付け」で圧縮されます。

そういうことのようです。
圧縮さえしてくれれば僕は満足です。

ちなみに、XPとVistaでファイル名が変わってしまっているので、こうなっています。
いえいえ無事動作して僕は満足です。




mirrorMan - コンボボックス風フォーム3

ソースの追加です。
selectタグをinput type=textに切り替える場合、表示文字列であるselectedIndex.lengthからテキストボックスのsizeを求めていたのですが、JScriptは内部文字コードをUNICODEで保持しているので、日本語2byte文字も1文字とカウントされます。このためフォルダ名に日本語が多く含まれると幅が足りなくなります。
そこでbyte長を返すようにしました。
RPthTxtBox.size = (new String(elm.value)).length;
を変更して、
RPthTxtBox.size = bLen(elm.value);
bLen()は、
function bLen(s){
var l = s.length, b = l;
function isWide(c){ return(0x80 <= c &&(c <= 0xff60 || 0xffa0 <= c)) }
for(i=0; i<l; i++) if(isWide(s.charCodeAt(i))) b++;
return(b);
}
です。





mirrorMan - ドラッグドロップ

ドラッグドロップの機能はjQueryにももちろんあり、divなどのボックス要素をドラッグ中もポインタにぶら下げて描画できるのですが、mirrorManには少し難がありました。ファイル名を持つのはpreタグだし、親のtdタグを箱ごと運ぶのもファイラのUIとしてちょっと変です。また、アイコン画像をひとつひとつレジストリから取得するのはブラウザのレイアウトエンジンをファイラとして使うのに、レスポンス性能以前です。代わりに、IEのプロパティ/メソッドを使用すれば、ポインタ画像も変わってくれるし、操作のレスポンスも許せるレベルです。

この機能のトピックは、フォルダに対してドロップされる以外はどこにドロップされてももう一方のペインにコピーを行うところです。つまり、コピーしたいファイル/フォルダをマウスでピッっとほんの少し飛ばす操作をするだけでコピーをしてしまいます。これは、コーディングの途中に偶然いいぞこれ!と、あまりにも楽なので、実はこれが一番mirrorManでよくやる操作だったりします。

with(document.body){
ondragstart = handleDragStart;
ondragenter = handleDragEnter;
ondragover = setEventRetFalse;
ondrop = handleDrop;
}
それぞれのイベントにハンドル関数を乗っけます。
function setEventRetFalse(){
event.returnValue = false;
}
ドラッグ中のマウスポインタが要素の上に来た時のブラウザのデフォルトの動作をキャンセルさせます。これは動作上(なぜだったか忘れた)、ondragoverイベントにかぶせる必要がありました。
function handleDragStart(){
if(jqContextMenuDsp()) return;
if(/^fNm|^Fol|^zip/.test(event.srcElement.className))
event.dataTransfer.effectAllowed ="move";
}
ユーザがマウスの左ボタンを押したままにしてドラッグが始まった時のイベントハンドラです。
まずプルダウンメニューが表示されている場合をはじいています。
IEの場合、なにか操作が起きるたびにwindowオブジェクトレベルのeventオブジェクトが共通して更新されます。何が起きても社長報告なので、パラメータ、つまり担当レベルの伝言によらずとも社長に聞けばOK的なオブジェクトですね。変な例えですが、リーダーが鬼軍曹的なプロジェクトでそんな案件もありました。MSではそんなことはないと思いますが。閑話。
そのイベントを起こした要素のクラス名がドラッグドロップさせるものであれば、dataTransferのeffectAllowedプロパティに初期値として、何かを移動中であるとして"move"をセットします。
function handleDragEnter(){
if(jqContextMenuDsp()) return;
setEventRetFalse();
if(/^pntFol|^Fol|^zip|^imgFol/.test(event.srcElement.className)){
selItm(event.srcElement,"drag");
event.dataTransfer.dropEffect ="move";
}else{ event.dataTransfer.dropEffect ="copy";}
}
ドラッグ中のアイテムが別の要素オブジェクトの上に来たときのイベントハンドラです。event.returnValueの値は常に更新されているようなので、ここでも明示的にfalseをセットします。
この時点でeventオブジェクトのsrcElementはマウスポインタが上に来た要素オブジェクトになっています。それがドロップ可能のフォルダであれば、マウスポインタが上に来たフォルダが次々にフォーカス表示されていきます。
dropEffectプロパティにも"move"を設定し、対象がフォルダでない場合、つまりファイルの場合はelseでおしなべて"copy"をセットしています。ここがファイルをマウスでピッと飛ばしてコピーするくだんの箇所です。シンプルで便利なお気に入りロジックです。
function handleDrop(){  i
setEventRetFalse();
if(/^pntFol|^Fol|^imgFol|^zip/.test(event.srcElement.className))
var rc = selectedItemToHere("move");
else var rc = selectedItemToHere("copy");
if(!rc) setTimeout("loadItms("+ selItmLR.innerText +")",0);
}
ドラッグ中のユーザがマウスのボタンを離したときのイベントハンドラです。同じく要素のクラス名を検査して、コピーor移動を行うselectedItemToHere()の戻り値ががfalseだった場合に画面を更新しています。
function selectedItemToHere(elm){
…省略
if((selItmObj)&&((src.id =="move")||(elm =="move"))){
var fnm = selItmObj.Name;
var strTyp = selItmObj.IsFolder ? " フォルダ" : " ファイル";
var strCmd ="移動";
var toHereItm = selItmObj;
}else if(document.selection.createRange().text){
var fnm = document.selection.createRange().text;
var toHereItm = ssh.NameSpace(getFol(getAdrBar(iLR))).ParseName(
document.selection.createRange().text);
if(toHereItm) var strTyp = toHereItm.IsFolder ? " フォルダ" : " ファイル";
}
if(!strTyp) return false;
var tgtFol = ssh.NameSpace(getFol(strTgtFol));
if(strCmd =="移動") tgtFol.MoveHere(toHereItm, 64);
else tgtFol.CopyHere(toHereItm, sshCpyOpt);
return setTimeout('loadItms(1)',0);
}
ロジックは見たままです。酒の勢いでここまで書きましたが、あとはもう一杯のむのでそれが抜けたら更新します。要は、最後にMoveHere()かCopyHere()かをしています。




mirrorMan - テキスト差分表示2

2ではソース抜粋です。

<button type=button id=kickDFBtn title="DFへ F12"><img src="./img/df.gif"></button>
を表示しているbuttonタグです。

if(!sfs.FileExists("DF\\DF.exe")) kickDFBtn.style.display ='none';
起動時にDF.exeが存在しなければ、 を非表示にしています。

function handleOnClick(){
var src = event.srcElement, cls = src.className.split(" ",1)[0];
if((/^fNm/.test(cls))&&(!/Gre/.test(cls))){
selItm(src);
if(/DFで実行/.test(document.title)) comDF();
}
switch(src.id){
case "kickDFBtn": chgTitlebarTxt("│DFで実行するファイルを選択");
}
}
クリックイベントハンドラです。
イベントが発生した(クリックされた)タグのクラス名を取得しています。
split()しているのは、jQueryがクラス名にブランク区切りで値を追加してくるのに対応するためです。
クラス属性にはファイルorフォルダ、表示色を設定しているので、これをtest()してチェックします。
クリックされたのがファイルで、かつ緑色(ZIPフォルダ、その他の特殊ファイル)でなければ、
selItm()でそれを選択(textRangeのselect())表示させます。
タイトルバーに"DFで実行"の文字列がマッチすれば、comDF()を呼び出します。
マッチしなければ、後続を処理します。
id属性がkickDFBtnなら、chgTitlebarTxt()を呼び出してタイトルバー文字列を変更します。

function chgTitlebarTxt(s){
var defNm = "mirrorMan", Nm = document.title;
document.title = Nm == defNm ? Nm += s : defNm;
document.selection.empty();
}
document.selection.empty()はフォーカス解除です。

次はDFを起動する箇所です。
function comDF(){
var df ="DF\\DF.exe";
document.title = "mirrorMan";
f(!sfs.FileExists(df)) return wsh.popup("DF.exeがありません",0,document.title,64);
var fnm = document.selection.createRange().text;
if(!fnm) return wsh.popup("DFに渡すファイルを選択してください",0,document.title,64);
var lPth = sfs.BuildPath(getFolderSub(getAdrBar(0)), fnm);
var rPth = sfs.BuildPath(getFolderSub(getAdrBar(2)), fnm);
if((sfs.FileExists(lPth))&&(sfs.FileExists(rPth)))
wsh.Run("\""+ df +"\" \""+ lPth +"\" \""+ rPth +"\"");
else wsh.popup("このファイルは両ペインにありません。\n\n"+ fnm,0,document.title,64);
}
タイトルバーを初期に戻しています。
textRangeオブジェクトから選択されているファイル名を取得します。
両方のアドレスバーからディレクトリパスを取得し、ファイル名をつなげてフルパスにBuildPath()しています。
DFはコマンドラインパラメータから開きたい2ファイルを指定できます。




mirrorMan - テキスト差分表示

せっかくの2画面表示です。ワンクリックでテキストファイルの差分を表示します。
使いやすいフリーウェアのDFを使いたかったので、作者のMYON氏にメールして許可をもらい、ZIPファイルに同梱しています。


右端の ボタンをクリックすると、タイトルバーに「DFで実行するファイルを選択」の文字が表示されます。
色が変わっているreadMe.txtの中身を確認しるため、クリックorEnterします。



DFが起動して、左右のreadMe.txtが並べて表示されました。
差異のある行が色分けして表示されます。
mirrorM"a"nが左側のファイルではmirrorM"o"nになっているのが確認できます。

DFの使い方の詳細は同梱のreadmeか、Verctorサイトのものを参照してください。




mirrorMan - シンクロフォルダ移動2

2ではソース抜粋です。

<button type=button id=syncLRBtn>
<img class=cmdBtn id=syncLRImg src="./img/chkon.png">
</button>

<script>
document.body.onclick = handleOnClick;

function handleOnClick(){
switch(event.srcElement.id){
case "syncLRBtn": toggleholdWin('syncLRImg');
}
}

function toggleholdWin(i){
var elm = document.all(i);
if(i=="syncLRImg")
elm.src = /on/.test(elm.src) ? "./img/chkoff.png" : "./img/chkon.png";
}
</script>
クリックイベントが発生したタグのid属性からtoggleholdWin()にimgタグのidを渡して呼び出しています。
toggleholdWin()ではimgタグのsrc属性値を変更して画像を切り替えています。
続いてフォルダ移動のシンクロ処理を行っている箇所です。
function chTargetDir(cls,recursiv,tgtNm){

(省略)

if(/chkoff/.test(syncLRImg.src)){
LstLoading(LR);
setTimeout("loadItms("+LR+")",0);
}else if(!recursiv){
if((!tgtNm)&&(sfs.GetBaseName(pth)==sfs.GetBaseName(getAdrBar(getLR(3),true)))){
chTargetDir("pntFol"+getLR(3),true);
}else if((tgtNm)&&(sfs.FolderExists(sfs.BuildPath(getAdrBar(getLR(3)),tgtNm)))){
chTargetDir("Fol"+getLR(3),true,tgtNm);
LstLoading(1);
setTimeout('loadItms(1)',0);
}
}
}
imgタグのsrc属性の画像ファイル名で振り分けます。
LstLoading()は、フォルダ内容の表示が完了するまで「loading..」と表示させています。
続いてsetTimeout("loadItms("+LR+")",0);が完了したらフォルダ内容が表示されます。
setTimeoutの0秒指定でマルチタスクっぽくなります。

JavaScriptはfunctionプロシジャの呼び出し時にパラメータ数が合わなければエラーになるということはないので、パラメータ"recursiv"の有無を利用して再帰呼び出しをています。
シンクロフォルダ移動のボタンがオンの画像だった場合、移動先のフォルダ名をパラメータとして渡し、
"recursiv"パラメータがtrueであれば、もう一方のアドレスバーのパスにフォルダ名を追加して表示させています。
さらに親フォルダへの移動も同様に、"tgtNm"パラメータの有無をみて、なければ親フォルダ名で再帰的に呼び出されています。

ちなみに、Shell.Applicationと、WScript.ShellやScripting.FileSystemObjectとで同じことを行うメソッドやプロパティがたくさんありますが、大体において画面表示に関わるものはShell.Application、内部的な処理にはWScript.ShellやScripting.FileSystemObjectが向いているようです。こんど一覧にしてみたいですね。




mirrorMan - シンクロフォルダ移動

サーバとローカルディスクとでフォルダの階層構造が同じになっているようなとき、片方のペインでフォルダをクリックして移動したらもう一方も同時に移動するような、シンクロフォルダ移動機能があれば便利です。

ただ、勝手に移動されたくない場合も多々あるので、オンオフを切り替えるボタンがあります。



とのボタをクリックすればトグルで切り替わります。チェックマークありで機能オンです。






たとえば上のような状態、
左側:C:\1_名無し
右側:C:\mirrorMan\1_名無し
でどちらかの 1_名無し フォルダをクリックorエンターキーで移動させます。


どちらも 1_名無し フォルダに移動しました。

移動先のフォルダ名がもう一方にあれば移動するという仕組みです。
上の階層に移動する( .. フォルダか、バックスペースキー)でも同様に移動します。

しかし、オン状態のまま無意識に使っていると戸惑うことが多いので、あると便利だけど普段はいらないという機能です。





mirrorMan - お気に入り機能2

2はソースです。
<span id=setLstArea></span>
<script>
setLstArea.innerHTML ="<ul id=setLstBar></ul>";
var ar = (new VBArray(dicIniLft.Keys())).toArray();
for(var i in ar) $(setLstBar).append("<li><a href=# class=setLstBtn>"+
"<span id="+ ar[i] +" class=setLstSpn>"+
ar[i].replace(/^f_/,"") +"</span></a></li>");
$(setLstBar).sortable({revert:true});
$('#'+ar[0]).css("color","limegreen");
</script>
jquery.uiのsortableはver1.5あたりからマウスを離した後にずりずりっと元の位置に戻るアニメーションがデフォルトでオフに変わっていますが、revert:trueオプションを指定することでオンになります。
お気に入りを追加するのはこんな感じです。
var btnNm = prompt("名前を入力してください。","");
if((btnNm == null)||(!btnNm)) return;
else btnNm = btnNm.replace(/^(\s+)(.+)/,"$2");
if(dicIniLft.Exists("f_"+ btnNm))
return wsh.popup("同じ名前のボタンが既にあります。",0,document.title,64);
if(/[\\\/:\*\?"'<>\|,;@{}\[\]()$$]/.test(btnNm)) //"
return wsh.popup(
"ボタン名に次の文字は使えません。\n\n"+
" \\ / : @ * ? $ \" \' <> | , {} [] ()",0,document.title,64);
$(setLstBar).append("<li><a href=# class=setLstBtn>"+
"<span id=f_"+btnNm+
" class=setLstSpn style=display:none>"+
btnNm+"</span></a></li>");
dicIniLft.Add("f_"+ btnNm,getAdrBar(0));
dicIniRht.Add("f_"+ btnNm,getAdrBar(2));
setLstBtnClear();
$('#f_'+ btnNm).css('color','limegreen');
if(/MSIE 6/.test(navigator.appVersion)) $('#f_'+btnNm).show();
else $('#f_'+btnNm).fadeIn("slow");
$(setLstBar).sortable("refresh");

ボタン名、つまりお気に入り名はdictionaryのKeyとして格納するので、
文字列のチェックが必要です。
まず先頭の空白があれば取り除きます。
同じKeyがあるか、使えない文字はないかを判定し、登録します。
その後の処理、jQueryは便利です。
ただ、IE6だとfadeIn()がうまく動作しないようなので、show()を使用しています。
並べ替えを可能にするには、これが実にsortable({})と書くだけです。すごい。

あと、マウスポインタが上にきたときのmouseoverイベントで背景画像を変えるのは、
Sliding Doorテクニックという、javascriptも使用しないなつかしい方法を使っています。sliding Doorとは引き戸のことですが、横に長い画像を文字列幅で切り詰めつつ、右どなりの要素には右端の画像を使うという方法です。ちょうど引き戸を閉めて壁に隠れていくイメージですね。これによって、長さの異なる文字列で登録されるお気に入りであっても、マウスが重なるとその範囲をフォーカス表示しています。





mirrorMan - お気に入り機能

このソフト最大のメリット、お気に入りボタンについてです。
初めて起動すると、「サンプル1」というボタンが表示され、
左ペインにマイドキュメント、右ペインにデスクトップが表示されます。




さっそく登録してみます。
保存したいフォルダのペアを左右ペインに表示させたら、
右クリックして「この状態を登録」を選択します。



プロンプトが表示されるので、登録したい名前を入力します。「新オキニ」と入力しました。


「新オキニ」が表示されました。
これでいつでも「新オキニ」のワンクリックで登録したフォルダのペアにアクセスできます。




「サンプル1」はもういらないので削除します。
カーソルを合わせて右クリックし、「削除」を選択します。




「サンプル1」が削除されて表示されなくなりました。




どんどん登録していきます。
クリックして表示したお気に入りの文字は緑色になります。



マウスでぐりぐりドラッグして順序を並べ替えられます。jQueryのSortableありがとう。



お気に入りは、終了時の状態が次回起動時にも表示されます。

慣れてくれば、mirrorManを起動したらファイルをサーバからコピーして開き、保存して赤い矢印ボタンでコピーという一連の作業がだいぶ楽だと思います。







mirrorMan - 一括コピーボタン2

2ではソースの抜粋です。
mirrorManはOSのファイルシステムとリアルタイムにリンクしていません。ファイルを色分けしして画面表示してから、一括コピーボタンが押下されるまでの間にファイルが更新されている可能性があります。これをキャッチしないとたとえば次のようなことが起きかねません。
・サーバの共有ファイルが「俺が更新したのに何で古いファイルをまた上書きしてんだよ」なんて非難されてしまう。
・自分がローカルで更新したのを忘れて、うっかりサーバから上書きしてしまい、既に脳内キャッシュ領域からも消去されてしまった作業が露と消えてしまう。

そういうわけで、以下の8つのケースをコピー時にチェックしています。前回で確認ダイアログはバカだといいつつ、そういうファイルが見つかるたびにダイアログを表示して問い合わせます。

■赤いファイルのコピー時
1.赤いファイルの更新日時をコピー時にも比較する。
2.赤いファイルがコピー先に存在しない。
3.赤いファイルがコピー先のほうが新しい。
4.赤くないファイルがコピー先よりも新しい。
5.画面に表示されていないがコピー先より新しいファイルが存在した。

■青いファイルのコピー時
1.青いファイルがコピー先に存在する。
2.青くないファイルがコピー先に存在しない。
3.画面に表示されていないがコピー先に存在しないファイルが存在した。

ソースは長ったらしく、自分で見るのもちょっといやです。
役所で窓口をたらい回しにされるような気分になります。
function copyColoredFile(elmid){
var lftFol = getFolderSub(getAdrBar(0));
var rhtFol = getFolderSub(getAdrBar(2));
if(/Rht/.test(elmid)){
var objErFol = sfs.GetFolder(lftFol);
var strErFol = lftFol, strEdFol = rhtFol, strEr ="Lft";
}else{
var objErFol = sfs.GetFolder(rhtFol);
var strErFol = rhtFol, strEdFol = lftFol, strEr ="Rht";
}
var cItm,edDLM,erDLM,erItm,fcItm,intBt,itmNm,tFol;
var clr = /red/.test(elmid);
var itms = document.getElementsByTagName('PRE');
パラメータはボタンのid属性です。左右ペインのアドレスバーの値からフォルダオブジェクト、他にパス、ボタン色などを取得しています。
  for(var i in itms){
if(!itms[i].className) continue;
if((clr)&&(itms[i].className.split(" ",1)[0] =="fNmRed"+strEr)){
itmNm = itms[i].innerText;
erItm = sfs.GetFile(sfs.BuildPath(objErFol,itmNm));
if(! sfs.FileExists(sfs.BuildPath(strEdFol,itmNm))){
intBt = wsh.popup("赤いファイル "+itmNm+" がコピー先に存在しません。\n"+
"このファイルのコピーを行いますか?",0,document.title,36);
if(intBt == 7) continue;
}else{
erDLM = new Date(erItm.DateLastModified);
edDLM = new Date(sfs.GetFile(sfs.BuildPath(strEdFol, itmNm)).DateLastModified);
if(erDLM < edDLM){
wsh.popup("赤いファイル "+itmNm+"\n\nコピー先のほうが新しくなっています。"+
"\nコピーを中止します。\n\n"+
"コピー元更新日時: "+ erDLM.toLocaleString() +"\n"+
"コピー先更新日時: "+ edDLM.toLocaleString() ,0,document.title,48);
loadItms(1);
return;
}
}
tFol = ssh.NameSpace(strEdFol);
cItm = ssh.NameSpace(strErFol).ParseName(itmNm);
tFol.CopyHere(cItm, sshCpyOpt);
コピペしたら説明するモチベーションが潰えました。要は最後から2行目でCopyHere()しているというわけです。オプションパラメタのsshCpyOptの設定値(>MSDN)は、INIファイルの[cpyOpt]の値で好みで変更できます。デフォルトは80です。

    }else if((!clr)&&(itms[i].className.split(" ",1)[0] =="fNmBle"+strEr)){
itmNm = itms[i].innerText;
erItm = sfs.GetFile(sfs.BuildPath(objErFol,itmNm));
if(sfs.FileExists(sfs.BuildPath(strEdFol,itmNm))){
erDLM = new Date(erItm.DateLastModified);
edDLM = new Date(sfs.GetFile(sfs.BuildPath(strEdFol, itmNm)).DateLastModified);
intBt = wsh.popup("青いファイル "+itmNm+"\n\nコピー先に存在します。\n"+
"このファイルの上書きコピーを行いますか?\n\n"+
"コピー元更新日時: "+erDLM.toLocaleString() +"\n"+
"コピー先更新日時: "+edDLM.toLocaleString() ,0,document.title,36);
if(intBt == 7) return loadItms(1);
}
tFol = ssh.NameSpace(strEdFol);
cItm = ssh.NameSpace(strErFol).ParseName(itmNm);
tFol.CopyHere(cItm, sshCpyOpt);
}
}
要は最後から2行目でCopyHere()しているというわけです。
  for(var i=0,fcer=new Enumerator(objErFol.files); !fcer.atEnd(); fcer.moveNext(),i++){
fcItm = fcer.item().Name;
if((clr)&&(sfs.FileExists(sfs.BuildPath(strEdFol, fcItm)))){
erDLM = new Date(fcer.item().DateLastModified);
edDLM = new Date(sfs.GetFile(sfs.BuildPath(strEdFol, fcItm)).DateLastModified);
if(erDLM > edDLM){
intBt = wsh.popup("赤い表示ではないファイル\n\n"+fcItm+"\n\nコピー先より新しくなっています。\n"+
"このファイルの上書きコピーを行いますか?\n\n"+
"コピー元更新日時: "+erDLM.toLocaleString() +"\n"+
"コピー先更新日時: "+edDLM.toLocaleString() ,0,document.title,36);
if(intBt == 7) continue;
tFol = ssh.NameSpace(strEdFol);
cItm = ssh.NameSpace(strErFol).ParseName(fcItm);
tFol.CopyHere(cItm, sshCpyOpt);
}
}else if((!clr)&&(!sfs.FileExists(sfs.BuildPath(strEdFol,fcItm)))){
intBt = wsh.popup("青い表示ではないファイル "+fcItm+"\n\nコピー先に存在しません。\n"+
"このファイルのコピーを行いますか?",0,document.title,36);
if(intBt == 7) continue;
tFol = ssh.NameSpace(strEdFol);
cItm = ssh.NameSpace(sfs.GetParentFolderName(fcer.item())).ParseName(sfs.GetFileName(fcItm));
tFol.CopyHere(cItm, sshCpyOpt);
}
}
setTimeout('loadItms(1)',0);
}
コピー対象の色ではないファイルがその配色と同じ状態だった場合を探すために、もう一度ループします。今度はファイル名の色は必要ないので、フォルダオブジェクトのコレクションで全てのファイルにアクセスします。画面に表示されていないファイルをここでキャッチします。要は最後から2行目でCopyHere()しているというわけです。





mirrorMan - 一括コピーボタン

ファイルやフォルダをドラッグ&ドロップすることでコピーや移動を普通にすることもできますが、
2ペイン型ならではの機能としてファイルの一括コピー機能があります。
赤と青の矢印ボタンがそれです。



同じ名前のファイルの更新日時を比較しています。
ファイル名の色分けは次のとおりです。

赤 :もう一方のペインよりも新しいファイル
赤紫 :もう一方のペインよりも古いファイル
青 :片方にしかないファイル



灰色: 更新日時も同一のファイルです。

この画面の場合は青いファイルを右ペインにコピーするだけなので、青の右矢印ボタンだけが表示されています。今回はこのボタン表示をグレイアウトさせるのではなく、表示されないように変更しました。



青い右矢印ボタンを押した結果の状態です。
青いファイルが右側にコピーされて、更新日時が同一なので灰色になりました。
青い右矢印ボタンも表示されなくなりました。

同様の動作を赤いボタンでも上書きコピーで行います。



矢印ボタンが4つとも表示されている最初の画面で赤い右矢印ボタンを押した結果です。
2_名無し.txtファイルだけが右にコピーされて灰色になり、赤右矢印ボタンも表示されなくなりました。

上書きコピーの場合、エクスプローラではいちいち確認ダイアログボックスが表示されますね。
そんなことはしません。
ダイアログに目を通すのがわずらわしくてろくに目を通さずにクリックするくせに、ダイアログが表示されるからと安心してしまっているから誤って間違いコピーをしてしまうのです(私の場合)。ユーザに確認を求めるポップアップ表示なんてものは、慎重性と習慣性の無限ループです(私にとっては)。
考える前にファイル名が赤ければいいのだと()。

4つのボタンを使い分けることで、パターン別にファイルのミラーリングが行えます。
また、ファイルの色づけ、ボタンの表示/非表示はフォルダを移動する度に行うので、
該当のファイルがあるかどうかは、ボタンが表示されているかどうかで一目でわかります。

ソースの紹介は次回に。




mirrorMan - コンボボックス風フォーム2

2ではソースの抜粋です。

右クリックメニューを表示するプラグインを読み込みます。
<script type="text/javascript" src="jquery.contextmenu.r2.packed.js" defer></script>

アドレスバー領域の要素です。
<span id=LftPthLst></span><span id=LftPthBox></span>

メニュー内容を作成します。class属性の指定で、プラグインによって起動後には非表示になります。
<div class="contextMenu" id="conMenuToAddBar">
<ul>
<li id="pasteToAddBar">貼り付け</li>
<li id="cpoyFrmAddBar">コピー  </li>
</ul>
</div>
右クリックメニュー項目選択時の処理です。色分けしたid属性値がひもづいています。
$('#LftPthLst').contextMenu('conMenuToAddBar',{
bindings:{
'pasteToAddBar': function(t){
switchPathBoxBtn(LftPthBox, clipboardData.getData("Text"));
},
'cpoyFrmAddBar': function(t){
clipboardData.setData("Text", LftPuldwn.options[LftPuldwn.options.selectedIndex].value);
}
}
});

function loadAdrBarItms(num,flg){
var resv = (new RegExp).compile(/^(\\\\.+\\)(.+?\\)/);
var relo = (new RegExp).compile(/^([A-Z]:\\).+(\\.+?)(\\.+?\\)/);
if(flg < 2){
var lHTML ="<select id=LftPuldwn class=lst0 onChange=chgLst(this.options[this.options.selectedIndex].value,0)>";
var itm,arLft = (new VBArray(dicLstLft.Keys())).toArray();
for(var i in arLft){
itm = String(dicLstLft(arLft[i]));
lHTML +="<option id='"+ arLft[i] +"' value='"+itm+"'>"+itm.replace(resv,'\\\\..\\$2').replace(relo,'$1..$3');
} lHTML +="</select>";
LftPthLst.innerHTML = lHTML;
var elm = document.all[arLft[num]];
elm.selected = true;
LftPthBox.innerHTML ="<input type=text class=txt0 id=LPthTxtBox value='"+ elm.value +"'>";
LPthTxtBox.size = (new String(elm.value)).length;
}
if(flg > 0){
var rHTML ="<select id=RhtPuldwn class=lst2 onChange=chgLst(this.options[this.options.selectedIndex].value,2)>";
var itm,arRht = (new VBArray(dicLstRht.Keys())).toArray();
for(var i in arRht){
itm = String(dicLstRht(arRht[i]));
rHTML +="<option id='"+ arRht[i] +"' value='"+itm+"'>"+itm.replace(resv,'\\\\..\\$2').replace(relo,'$1..$3');
} rHTML +="</select>";
RhtPthLst.innerHTML = rHTML;
var elm = document.all[arRht[num]];
elm.selected = true;
RhtPthBox.innerHTML = "<input type=text class=txt2 id=RPthTxtBox value='"+ elm.value +"'>";
RPthTxtBox.size = (new String(elm.value)).length;
}
}
プルダウンリストとテキストボックスの両方のhtmlを記述しています。とくにモダンなことはしていません。仕事場なんかだと長いフォルダ名や深い階層が多いので、パスを省略表示するための正規表現をfor文の中で何回も(リストの項目数分)使用するので、あらかじめ内部形式にコンパイルしているくらいでしょうか。あとは、パラメータ変数flgは左ペインなら0、右ペインなら2、両方なら1という値で左右の処理の有無を分けています。テキストボックスの幅は、文字数から求めています。
$(LftPthBox).toggle();
起動時はプルダウンリストのみを表示するので、テキストボックスを格納するspan要素はtoggle()メソッドで非表示にします。
function switchPathBoxBtn(elm, clp){
if(/^L/.test(elm.id)){
var box=LftPthBox, tox=LPthTxtBox, lst=LftPthLst, dwn=LftPuldwn;
}else{
var box=RhtPthBox, tox=RPthTxtBox, lst=RhtPthLst, dwn=RhtPuldwn;
}
if(box.style.display == "inline"){
box.style.display ="none";
lst.style.display ="inline";
}else{
box.style.display ="inline";
tox.value = clp || dwn.options[dwn.options.selectedIndex].value;
tox.width = lst.offsetWidth;
lst.style.display ="none";
txtBoxFocus(tox);
}
try{ resizeTo(currentWindowSize.width,currentWindowSize.height) }catch(e){}
winPositionize();
}
プルダウンリストとテキストボックスを切り替えるメインです。上で使っているjQueryのtoggle()を使えばいいかとも思いますが、toggle()はvisibiltyで切り替えているのでノードオブジェクト自体は消されません。起動時はむしろこっちのほうが安全ですが、displayプロパティならノードオブジェクトも消えるので、自前でやるならこっちのほうが安定するという意図です。
左ペインの処理か右ペインかでhtmlノードオブジェクトをそれぞれひとつの変数に格納して、displayプロパティを切り替えます。
テキストボックスの値は、右クリックメニューで貼り付けを選択された場合にパラメータでクリップボードデータが渡されていればそれを、なければプルダウンリストで選択されている値です。最後の2行は、画面の位置とサイズを調整する呼び出しです。HTAの場合、resizeTo()処理時にマウスのボタンが押されたままの状態だとエラーになるので、try{}catch{}で逃がします。

「 HTMLでコンボボックス風に マウス長押しで切替えてみる」も。





mirrorMan - インクリメンタルサーチ

エクスプローラのフォルダウインドウでファイル名をタイプすると、その文字にマッチするファイルにフォーカスが移ります。「m」と入力するとファイル名の頭に「m」がついているファイル、続けて「irr」と入力すると「mirr」がついているファイルという具合に、連続したキー入力でフォーカスが移動していくのを目で確認しながら、目的のファイルを探し出すことができます。
FFFTPでサーバの大量ファイルを探すときにはおなじみの機能で、Vistaでも検索ウインドウやスタートメニューのファイル名を指定して実行などで取り入れられています。たとえばエクセルを使うなら、ウインドウズキーを押してexcelと入力すれば最近使ったエクセルファイルがすぱっと一覧で表示されるので、UIとしては最短距離ではないでしょうか。PCのアイドル時にひたすらカリカリとインデックスを作成しているだけあります。評判が悪いというか地に堕ちたVistaですが、検索機能は職場でも使いたいです。元々Mac派でWindows95のときは憎しみすら覚えたクチなんですが、ごく短い期間ですがMSで働いたこともあって、今はWindowsに同情する側です。

ソースの該当部分ですが、まず、
document.body.onkeydown = handleKeydown;
onkeydownをhandleKeydownにオーバーライドします。参照ではないので、()は付けません。


function handleKeydown(){
var ky = event.keyCode;
var shKy = event.shiftKey, ctKy = event.ctrlKey, alKy = event.altKey;
if((!ctKy)&&(!shKy)&&(!alKy))
if(((ky>=48)&&(ky<=57)) || ((ky>=65)&&(ky<=90)) || ((ky>=97)&&(ky<=111)))
return selItmByKeyDown(ky);
}
Shiftキーや、Ctrlキー、Altキーの押下状態は、IEの場合トップレベルのeventオブジェクトにあります。
これらのキーが押されている場合をまず除外します。さらに、0-9、A-Z、a-zの範囲だった場合に処理を行います(テンキーの0-9は別のkeyCodeですが対応していません)。onkeydownにかぶせる処理はここまでです。

次に画面の動きです。

たとえば「qwef」と入力すると、

1.3秒間、このようにキー入力をプールした状態を表示します。1.3秒間は入力追加とし、最初に入力したキーからそれ以上経過するとクリアします。そして、ここに表示している文字列をもとにファイル名の検索を行います。インクリメンタルサーチ入力の閾値が1.3秒で、それ以降は新しい入力だというわけです。短いと入力し切れないし、長いと次の新しい入力を待ってイライラ、とあんばいがムズカシスところです。


「b」をタイプしたタイミングで「bac」フォルダにフォーカスします。続けて「a」をタイプしても、まだ文字はマッチしているので1.3秒間以内であればフォーカスは移りません。さらに「k」をタイプして「bak」となれば「bak」フォルダにフォーカスが移ります。


ソースです。
function clearKeyDownPool(){
if(keyDowns.innerText) keyDowns.innerText ="";
}
setTimeout()で1.3秒後に文字列をクリアする処理を関数で用意します。
文字列を表示しているspanタグはid=keyDownsです。

function selItmByKeyDown(k){
k = String.fromCharCode(k).toLowerCase();
if(k != keyDowns.innerText){
setTimeout('clearKeyDownPool()',1300);
k = keyDowns.innerText += k;
}
var elms = document.getElementsByTagName('PRE');
var t, i = cntSelItmId();
for(; i<elms.length; i++){
t = elms[i].innerText;
if((t.indexOf(k) == 0)||(t.indexOf(k.toUpperCase()) == 0)){
if((i == cntSelItmId())&&(k.length < 2)) continue;
selItmLR.innerText = /Lft/.test(elms[i].id) ? 0 : 2;
cntSelItmId(elms[i].id.replace(/...(\d+)/,'$1'));
selItm(elms[i]);
break;
}
}
}
1行目でkeyCodeを文字にしています。
2~5行目では、同じ文字の入力だった場合をはじいています。なぜなら、たとえばa、a、aと入力された場合は、1.3秒なんて待たずに「a~」という名前のファイルに次々とフォーカスが移るべきだからです。それに、頭から同じ文字が続くファイル名なんてほとんどみたことがありません。
画面上では、4行目でinnerText値の更新を処理し、;でコミットしたタイミングで追加入力している文字列が表示されるはずですね。少なくとも、次のfor文の中でinnerTextを取得する段階ではそうなっていないと動作しません。
6行目以降は、ファイルの一覧から一致する名前を探し出す処理です。
htmlの要素の値として持てる文字列と、ファイル名として持てる文字列とは制限に違いがあるので、ファイル名の表示はpreタグに入れてあります。それをgetElementsByTagName()でノードオブジェクトを取得し、画面に表示されているファイルのすべての名前にアクセスできるようにします。
cntSelItmId()は現在フォーカスされているアイテムをid属性値からたどってファイルオブジェクトを取得するためのインデックス値を格納するための処理で、上の画像の数字部分に表示しています。常に画面に表示しておくことで、グローバル変数としてどこからでもアクセスできること、フォーカスがいまどこにあるのかを見れることが目的です。詳細はここでは省略します。
selItm()は、ファイル名を反転表示させてフォーカスを示す処理です。実際はIEのフォーカスではなくて、textRangeオブジェクトのselect()ですが、これも詳細は省略します。





mirrorMan - コンボボックス風フォーム

comboboxとは、プルダウンメニューとテキスト入力ボックスとがコンボ化したフォーム。
アプリケーションでは一般的ですが、HTMLフォームにはありません。
jQueryのUIライブラリにはコンボボックス部品があり、色々試してみましたが、思うように動作するものがありませんでした。そこでIE標準フォームを使ってそれ風のものを作っています。

IE7では、selectタグがondblclickイベントを受け付けられるようになっています。

アドレスバー領域をダブルクリックすると、




テキストボックスに切り替わります。Ctlr+Vでパスを貼りつけたり、直接入力することができます。




IE6の場合、右の移動ボタンを押すか、もしくはプルダウンリストを右クリックして出るメニューから「貼り付け」を選択してテキストボックスへの切り替えます。




あるいはまだ存在しないフォルダを追加入力してEnterキーを押すと、フォルダを作成するかどうかのダイアログが表示されて、

そのフォルダに移動します。
作成したフォルダに移動まで行うのは少し押し付けがましいかもしれませんが、自分ではこれが便利です。
エクスプローラの、右クリックでフォルダを作成してそのフォルダを名前変更して開く、という作業よりは手数が少なくて済みます。

とはいえ、手に染みついたエクスプローラのこの機械作業、やっぱり同じ方法でフォルダを作ることが多いです。











右クリックメニューから「新規フォルダ」を選択します。











「1_名無し」という新しいフォルダが作成され、テキストボックスに入力可能な状態でフォーカスして表示します。
右クリック選択後、右手をキーボードに戻してそのままフォルダ名を入れられます。エンターキーで確定です。

「1_名無し」という初期フォルダ名が2ch的で仕事で使いづらいという注文は、作者の趣味の問題なので答えられません。








mirrorMan - フォルダ参照

無題
いわゆる特殊フォルダを扱うのに難があったため、無効になっていた browsfol.pngボタンを動作するようにしました。押すとフォルダ参照画面が表示されます。




folview.png






コンピュータを選ぶと、
comv.jpg

アドレスバーに「コンピュータ」が表示され、アイテムを表示します。

Windowsにはいろんなタイプのフォルダがあり、そのすべてがNameSpace()で同じように更新日時のプロパティや、対象ファイルのオブジェクトを取得したりできるわけではありません。
「コンピュータ」なら、レジストリで「コンピュータ」という名前にひもづく物理的なCLSIDがあり、内部から扱うにはこちらからオブジェクトを取得しなければなりません。


そのため、Scripting.Dictionaryに「コンピュータ」というKeyと「::{20D04FE0-3AEA-1069-A2D8-08002B30309D} 」というItemの組みあわせを記憶させておき、必要に応じて「コンピュータ」KeyにひもづけてCLSIDを取得します。そして、Dictionaryの情報のうち次回起動時にあらかじめ取得しておくべきものをINIファイルに書き込んでおきます。

var ss ="",sp ="",spcs =[];
var arK = (new VBArray(dicSpecil.Keys())).toArray();
var arI = (new VBArray(dicSpecil.Items())).toArray();
$("span.setLstSpn").each(function(i){
ss +="[setLst]\t"+ this.id +"\t"+
dicIniLft.Item(this.id) +"\t"+ dicIniRht.Item(this.id) +"\r\n";
for(var i in arK){
if((arI[i] == dicIniLft.Item(this.id))||(arI[i] == dicIniRht.Item(this.id))){
spcs.push(arK[i]);
for(var j in spcs) if(arK[i] == spcs[j]) var k = 1;
if(k) sp +="[spcLst]\t"+ arK[i] +"\t"+ arI[i] +"\r\n";
}
}
});

JScriptにはINI書き込みの標準関数の類はないので、自分でやります。dicSpecilオブジェクトがDictionaryです。jQueryの$()で指定しているclass属性setLstSpnは画面左上のmzip、最新版等のユーザ登録のボタンリストのものです。これをeach()メソッドで回しています。後の細かい動作の説明はここでは省略します。





mirrorMan - 正規表現フィルタ

大量のファイルからキーワードで検索するとき、思わず正規表現でしたくなることがあります。思わずしたくなるといっても性器表現ではありません。Regular、「正規」。この日本語訳もどうかと思います。今はやりの雇用問題でいうと「非正規」社員は存在が「イレギュラー」なメンバーかということになってしまいます。正規表現はそういう意味での正規な表現ではなくて、
そんなことはさておき、エクスプローラのワイルドカード検索のかわりに正規表現でフィルタします。

右クリックメニュー内のテキストボックスにキーワードを入力し、エンターキーで実行します。右の画像の場合、mirで始まり.htaで終わるファイルを抽出しています。キャンセルはESCキーです。




ソースはこんな感じです。
function jqContextMenuDsp(){ //contextmenu.r2.js:singleton menu id
if(! document.all.jqContextMenu) return false;
return (jqContextMenu.style.display =="block");
}
右クリックメニューに使用しているcontextmenu.r2.jsはsingletonなノードIDがあるので、これを利用しています。
IEの場合、存在しないノード(タグのid属性)を参照すると、変数が定義されていないとエラーを返すので、document.allからノードツリーをたどって確認します。存在すれば、displayプロパティがblockかどうかを返します。
if(jqContextMenuDsp()){
try{ var re = filterLstBox[2].value?new RegExp(filterLstBox[2].value,'i'):/./}
catch(e){wsh.popup(e.description, 1,document.title,64); return}
}
テキストボックスに値が入力されていれば、オブジェクト変数reにRegExpでnewしておきます。
try{}catch{}しているのは、ユーザが入力した正規表現のコンパイルエラーを逃がすためです。
正規表現式でなかった場合、このようなポップアップで知らせます。

regpop.png

あとは、フォルダのコレクションで回してhtmlを生成する部分で次のようにひっかけています。
var itm, ssh = new ActiveXObject("Shell.Application");
var erfo = ssh.NameSpace(er);
for(var fc=new Enumerator(erfo.Items()); !fc.atEnd(); fc.moveNext()){
itm = fc.item();
if(re) if(!re.test(itm)) continue;

}





mirrorMan1.58.zip

フォルダ遷移ごとに画面がフェードインするように変更。
$('td').toggle().fadeIn(300);

あとボタンクリックで日時を表示する部分、月表示が間違っていたので修正した。
ctimer.jpg
<span id=lftStatsbar></span>
function clockTimer(){
var timer = 0;
return function(){
if(timer) clearTimeout(timer);
else return timer = setTimeout('innerbull()',1000);
}
}
function innerbull(){
var d = new Date();
lftStatsbar.innerText = d.getYear() +"/"+
(d.getMonth()+1) +"/"+
d.getDate() +" "+
d.toLocaleTimeString();
tglClock = clockTimer();
tglClock();
}

d.getMonth()+1は前後処理の解釈から文字列で+してしまうので、
2008/61/12 12:59:00
となってしまっていた。(d.getMonth()+1)とすれば()内のみで型判定して+するので、
2008/7/12 12:59:00
だ。

このモジュールは、クロージャでタイマIDの変数timerを保持し、毎秒clearTimeout()してはDateオブジェクトを生成してsetTimeout()するという無駄なことをやっている。でも次のような場合に便利。たとえばフォルダ移動などの別のユーザ操作があったら日時表示を消したいときには、表示タグのinnerTextを毎秒上書きしているこのループ処理を止めなければならない。そんな箇所はあちこちにあったりする。あちこちでタイマIDをハンドルしてclearTimeout()してたらソースがちらかり、デバッグするのも気が重くなってしまう。上のようにしておけば、tglClock();と記述するだけでループの有無も気にすることなく止めることが可能だ。





ver1.5.4

JQueryが1.2.6にバージョンアップしていたので更新。
uiのほうは結構変わっているようだが動作確認のみ。
mouse.jsとかdimension.jsを個別に読み込む必要があったが、
jquery.ui.all.jsを読み込んでおけばまとめてokなようだ。

mirrorMan本体は、ドラッグオーバー時の反転表示をしなくなっていたのを修正。

ObjectタグでShell.FolderViewに接続してEnumDoneイベントを受け取れば、
mirrorManからでないフォルダ内の更新時にも画面更新できそうだが、今だにやり方がわからない…




mirrorMan

mirrorMan

mirrorManはHTAとJScriptでつくったフリーの2画面ファイラです。

ファイルはローカルで開いて更新したい、でもサーバ上でも最新状態を保ちたい。
でも複数のエクスプローラを開いておくのは邪魔だし、
更新が終わるたびに開きなおすのも面倒です。

そこで、ローカルとサーバのよく使うフォルダのペアをお気に入りとして登録しておき、一発で開きます。
さらにファイルの更新日時を比較し、ファイル名を配色表示します。
ファイルが複数あれば右上の矢印ボタンで一括コピーもします。

PC雑誌の『iP!』や『Windows100%』のフリーウェアライブラリに載りました。
Vectorにも登録してあります。

作者本人にはもう必須のソフトですが、
身の回りで継続して使っている人はあまりいません。
エディタで開けばソースは丸見えですし、どうか使い勝手の悪さをつっこんでください。




while(aho.atEndofStream)

笹部 政宏
笹部 政宏
mail




フリーソフツ
Category
はてブ
Monthly Archive
New Entry
New Comment
New Trackback
RSS
Copyright © Kittens flewby me All Rights Reserved.