//
//  KMThreadList.m
//  BathyScaphe
//
//  Created by Hori,Masaki on 11/07/18.
//  Copyright 2011 masakih. All rights reserved.
//

#import "KMThreadList.h"
#import "ThreadsListTable.h"
#import "KMLogDocumentWindowController.h"

#import "BoardListItem.h"
#import "BSDBThreadList.h"
#import "CMRMainMenuManager.h"
#import "BSThreadsListOPTask.h"

#import <SGAppKit/SGAppKit.h>
#import "BSQuickLookPanelController.h"
#import "CMRTextColumnCell.h"
#import "AppDefaults.h"
#import "CMXMenuHolder.h"

#import "KMWorkerEmulator.h"

#import "DatabaseManager.h"

#import "BoardManager.h"
#import "CMRBrowser_p.h"
#import "CMRAppDelegate.h"


NSString *const CMRBrowserThListUpdateDelegateTaskDidFinishNotification = @"CMRBrowserThListUpdateDelegateTaskDidFinishNotification";


NSString *KMThreadListDidUpdateNotification = @"KMThreadListDidUpdateNotification";

@interface KMThreadList()
@property CMRAutoscrollCondition keepConditionForScrolling;
@property (retain) NSArray *keepSelectedThreadPathForScrolling;
@property (retain, readwrite) BSDBThreadList *datasource;


- (void)registerKeepCondition:(CMRAutoscrollCondition)type;
- (void)clearKeepCondition;

@end

@interface KMThreadList(ViewBuilding)
- (void)setupThreadsListTable;
- (void)updateTableColumnsMenu;
@end

@interface KMThreadList(KM_NSTableViewDelegate) <NSTableViewDelegate>
- (void)saveBrowserListColumnState:(NSTableView *)targetTableView;
@end

@interface KMThreadList(RebuildDatabase)
- (void)showBrowserCriticalAlertMessage:(NSString *)messageTemplate informative:(NSString *)informativeText help:(NSString *)helpAnchor didEndSel:(SEL)didEndSelector;
@end


@implementation KMThreadList
@synthesize menu = _menu;
@synthesize threadListView = _threadListView;
@synthesize selection = _selection;
@synthesize datasource = _datasource;

@synthesize keepConditionForScrolling = _keepConditionForScrolling;
@synthesize keepSelectedThreadPathForScrolling = _keepSelectedThreadPathForScrolling;


+ (NSString *)localizableStringsTableName
{
#define APP_TVIEW_LOCALIZABLE_FILE			@"ThreadViewer"
	return APP_TVIEW_LOCALIZABLE_FILE;
}

- (id)init
{
	self = [super initWithNibName:@"KMThreadList" bundle:nil];
	if(self) {
		worker = [[KMWorkerEmulator alloc] init];
		
		
		NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
        DatabaseManager *dbm =[DatabaseManager defaultManager];
        [nc addObserver:self
               selector:@selector(updateThreadsListNow:)
                   name:DatabaseDidFinishUpdateDownloadedOrDeletedThreadInfoNotification
                 object:dbm];
        [nc addObserver:self
               selector:@selector(updateThreadsListPartially:)
                   name:DatabaseWantsThreadItemsUpdateNotification
                 object:dbm];
		[nc addObserver:self
			   selector:@selector(threadsListDownloaderShouldRetryUpdate:)
				   name:ThreadsListDownloaderShouldRetryUpdateNotification
				 object:nil];
		[nc addObserver:self
			   selector:@selector(threadDocumentDidToggleDatOchiStatus:)
				   name:CMRAbstractThreadDocumentDidToggleDatOchiNotification
				 object:nil];
		[nc addObserver:self
			   selector:@selector(threadDocumentDidToggleLabel:)
				   name:CMRAbstractThreadDocumentDidToggleLabelNotification
				 object:nil];
	}
	return self;
}
- (void)dealloc
{
	[worker release];
	[_selection release];
	[_keepSelectedThreadPathForScrolling release];
	[_datasource release];
	
	[[NSNotificationCenter defaultCenter] removeObserver:self];
	
	[super dealloc];
}

- (void)awakeFromNib
{
	[self setupThreadsListTable];
}

- (void)setRepresentedObject:(id)representedObject
{
	if([self representedObject] == representedObject) return;
	if(![representedObject isKindOfClass:[BoardListItem class]]) return;
	if([BoardListItem isFolderItem:representedObject]) return;
	
	[super setRepresentedObject:representedObject];
	
	self.datasource = [[[BSDBThreadList alloc] initWithBoardListItem:representedObject] autorelease];
	NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
	[nc addObserver:self
		   selector:@selector(listDidChangeNotification:)
			   name:CMRThreadsListDidChangeNotification
			 object:_datasource];
	[nc addObserver:self
		   selector:@selector(threadsListWantsPartialReload:)
			   name:BSDBThreadListWantsPartialReloadNotification
			 object:_datasource];
	
	[self.threadListView setDataSource:_datasource];
	
	// sort column change
	NSString *boardName = self.datasource.boardName;
	NSArray *foo = [self.threadListView sortDescriptors];
	NSArray *bar = [[BoardManager defaultManager] sortDescriptorsForBoard:boardName];
	[self.threadListView setSortDescriptors:[[BoardManager defaultManager] sortDescriptorsForBoard:boardName]];
	// SortDescriptors が変わらない時は tableView:sortDescriptorsDidChange: が呼ばれない。
	// これは困るので、無理矢理呼ぶ。
	if ([foo isEqualToArray:bar]) {
		[[self.threadListView dataSource] tableView:self.threadListView
						   sortDescriptorsDidChange:foo];
	}
	
	[self reloadThreadsList:nil];
}

- (NSString *)filterString
{
	return self.datasource.searchString;
}
- (void)setFilterString:(NSString *)filterString
{
	[_datasource filterByString:filterString];
	[self.threadListView reloadData];
}
- (void)setViewMode:(BSThreadsListViewModeType)mode
{
	[_datasource setViewMode:mode];
}
- (BSThreadsListViewModeType)viewMode
{
	return self.datasource.viewMode;
}
+ (NSSet *)keyPathsForValuesAffectingBaordName
{
	return [NSSet setWithObjects:@"datasource", @"datasource.boardName", nil];
}
- (NSString *)boardName
{
	return self.datasource.boardName;
}
+ (NSSet *)keyPathsForValuesAffectingNumberOfFilteredThreads
{
	return [NSSet setWithObjects:@"datasource", @"datasource.numberOfFilteredThreads", nil];
}
- (NSUInteger) numberOfFilteredThreads
{
	return self.datasource.numberOfFilteredThreads;
}
- (void)listDidChangeNotification:(id)notification
{
	[self.threadListView reloadData];
    UTILNotifyName(CMRBrowserThListUpdateDelegateTaskDidFinishNotification);
	UTILNotifyName(KMThreadListDidUpdateNotification);
}
- (void)tableViewSelectionDidChange:(NSNotification *)notification
{
	NSMutableArray *array = [NSMutableArray array];
	
	NSIndexSet *selectedIndexes = [self.threadListView selectedRowIndexes];
	NSUInteger row = [selectedIndexes firstIndex];
	while(row != NSNotFound) {
		[array addObject:[_datasource threadAttributesAtRowIndex:row inTableView:self.threadListView]];
		row = [selectedIndexes indexGreaterThanIndex:row];
	}
	
	[self registerKeepCondition:CMRAutoscrollWhenThreadUpdate];
	self.selection = array;
	
	BSQuickLookPanelController *qlc = [BSQuickLookPanelController sharedInstance];
    NSTableView *tableView = [notification object];
    if ([qlc isLooking]) {
        [_datasource tableView:tableView quickLookAtRowIndexes:[tableView selectedRowIndexes] keepLook:YES];
    }
}

- (void)selectThreadWithAttributes:(NSArray *)attrs scrollToVisible:(BOOL)scroll
{
	if(!attrs || [attrs count] == 0) return;
	
	NSMutableArray *paths = [NSMutableArray arrayWithCapacity:[attrs count]];
	
	for(id attr in attrs) {
		id path = [attr path];
		if(path) [paths addObject:path];
	}
	
	NSIndexSet *indexes = [self.datasource indexesOfFilePathsArray:paths ignoreFilter:NO];
	if(!indexes || [indexes count] == 0) {
		[self.threadListView deselectAll:nil];
		[self.threadListView scrollRowToVisible:0];
		return;
	}
	
	NSUInteger index = [indexes lastIndex];
    if(![[self.threadListView selectedRowIndexes] containsIndexes:indexes]) { // すでに選択されているか確認
        [self.threadListView selectRowIndexes:indexes byExtendingSelection:NO];
    }
    if(scroll) {
        [self.threadListView scrollRowToVisible:index];
    } else {
        CMRAutoscrollCondition type = self.keepConditionForScrolling;
        if (type != CMRAutoscrollWhenThreadUpdate && type != CMRAutoscrollWhenThreadDelete) {
            [self.threadListView scrollRowToVisible:0];
        }
    }
}

- (void)updateThreadsListNow:(NSNotification *)notification
{
    [_datasource updateCursor];
}
- (void)updateThreadsListPartially:(NSNotification *)notification
{
    // スマート掲示板の場合は、自動ソートが無効になっていても「自動ソート」せざるを得ない。
    if (self.datasource.viewMode == BSThreadsListShowsSmartList) {
        [_datasource updateCursor];
        return;
    }
	
    NSDictionary *userInfo = [notification userInfo];
    NSArray *files = [userInfo objectForKey:UserInfoThreadPathsArrayKey];
    if (files) {
        [_datasource cleanUpThreadItem:files];
    } else {
        BOOL isInsertion = [[userInfo objectForKey:UserInfoIsDBInsertedKey] boolValue];
        if (isInsertion && (self.datasource.viewMode == BSThreadsListShowsStoredLogFiles)) {
            [_datasource updateCursor];
        } else {
            [_datasource updateThreadItem:userInfo];
        }
    }
}

- (void)threadsListWantsPartialReload:(NSNotification *)notification
{
    UTILAssertNotificationName(notification, BSDBThreadListWantsPartialReloadNotification);
    id indexes = [[notification userInfo] objectForKey:@"Indexes"];
	
    if (indexes == [NSNull null]) {
        [self.threadListView reloadData];
    } else {
        NSIndexSet *allColumnIndexes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, [self.threadListView numberOfColumns])];
        [self.threadListView reloadDataForRowIndexes:indexes columnIndexes:allColumnIndexes];
    }
		
    if (self.keepSelectedThreadPathForScrolling) {
        [self clearKeepCondition];
    }
}

- (void)registerKeepCondition:(CMRAutoscrollCondition)type
{
	NSIndexSet *rows = [self.threadListView selectedRowIndexes];
    NSArray *tmp = nil;
    if (rows && ([rows count] > 0)) {
        tmp = [_datasource tableView:self.threadListView
					threadFilePathsArrayAtRowIndexes:rows];
    }
	self.keepSelectedThreadPathForScrolling = tmp;
	self.keepConditionForScrolling = type;
}
- (void)clearKeepCondition
{
	self.keepConditionForScrolling = CMRAutoscrollNone;
	self.keepSelectedThreadPathForScrolling = nil;
}

- (void)threadDocumentDidToggleDatOchiStatus:(NSNotification *)aNotification
{
    NSString *path = [[aNotification userInfo] objectForKey:@"path"];
    [_datasource toggleDatOchiThreadItemWithPath:path];
}

- (void)threadDocumentDidToggleLabel:(NSNotification *)aNotification
{
    NSString *path = [[aNotification userInfo] objectForKey:@"path"];
    NSUInteger code = [[[aNotification userInfo] objectForKey:@"code"] unsignedIntegerValue];
    [_datasource setLabel:code forThreadItemWithPath:path];
}

@end

#import "KMThreadDeleteCenter.h"
#import "CMRReplyDocumentFileManager.h"
#import "CMRTrashbox.h"
@interface KMThreadList(KMThreadDeleteCenterDelegate) <KMThreadDeleteCenterDelegate>
@end
@implementation KMThreadList(KMThreadDeleteCenterDelegate)
- (NSWindow *)window
{
	return self.view.window;
}
@end

#import "CMRFavoritesManager+KMAddition.h"

@implementation KMThreadList(Actions)
- (NSArray *)selectedThreadsReallySelected
{
	NSTableView *tableView = self.threadListView;
	NSIndexSet	*selectedRows = [tableView selectedRowIndexes];
	if (!_datasource || !selectedRows || [selectedRows count] == 0) {
		return [NSArray array];
	}
	
	return [_datasource tableView:tableView threadAttibutesArrayAtRowIndexes:selectedRows exceptingPath:nil];
}
- (NSArray *)clickedThreadsReallyClicked
{
    NSTableView *tableView = self.threadListView;
    NSInteger clickedRow = [tableView clickedRow];
    BOOL clickedOnMultipleItems = NO;
    
    if (clickedRow != -1) {
        clickedOnMultipleItems = [tableView isRowSelected:clickedRow] && ([tableView numberOfSelectedRows] > 1);
    } else {
        return [NSArray empty];
    }
    
    if (!clickedOnMultipleItems) {
        NSDictionary *attributes = [_datasource threadAttributesAtRowIndex:clickedRow inTableView:tableView];
        return [NSArray arrayWithObject:attributes];
    } else {
        return [self selectedThreadsReallySelected];
    }
}
- (NSArray *)selectedThreads
{
    NSTableView *tv = self.threadListView;
    NSArray *array = [_datasource tableView:tv threadAttibutesArrayAtRowIndexes:[tv selectedRowIndexes] exceptingPath:nil];
    if (!array) {
        array = [NSArray array];
    }
    
    return array;
}
- (NSArray *)targetThreadsForSender:(id)sender
{
    if (sender && [sender respondsToSelector:@selector(tag)]) {
        NSInteger senderTag = [sender tag];
        if (senderTag > 779 && senderTag < 784) {
            return [self clickedThreadsReallyClicked];
        }
    }
	
	NSArray *result = [self selectedThreadsReallySelected];
	if ([result count] == 0) {
		result = [self selectedThreads];
	}
	
	return result;
}


- (IBAction)addFavorites:(id)sender
{
	NSArray *selectedThreads;	
	selectedThreads = [self targetThreadsForSender:sender];
	
    if (!selectedThreads || ([selectedThreads count] == 0)) {
        return;
    }
	
	[[CMRFavoritesManager defaultManager] doActionWithThreadAttributes:selectedThreads];
}
- (IBAction)deleteThread:(id)sender
{
	NSArray			*targets_ = [self targetThreadsForSender:sender];
	KMThreadDeleteCenter *center = [[[KMThreadDeleteCenter alloc] init] autorelease];
	center.delegate = self;
	[center showAlert:CMRThreadViewerDeletionAlertType targetThreads:targets_];
}

- (IBAction)cleanupDatochiFiles:(id)sender
{
    [self showBrowserCriticalAlertMessage:@"CleanupDatochiFilesAlert(BoardName %@)"
                              informative:@"CleanupDatochiFilesMessage"
                                     help:@"CleanupDatochiFilesHelpAnchor"
                                didEndSel:@selector(cleanupDatochiFilesAlertDidEnd:returnCode:contextInfo:)];
}

- (IBAction)rebuildThreadsList:(id)sender
{
    [self showBrowserCriticalAlertMessage:@"RebuildThreadsListAlert(BoardName %@)"
                              informative:@"RebuildThreadsListMessage"
                                     help:@"RebuildThreadsListHelpAnchor"
                                didEndSel:@selector(rebuildThreadsListAlertDidEnd:returnCode:contextInfo:)];
}

- (IBAction)copyThreadAttributes:(id)sender
{
	NSArray *array_ = [self targetThreadsForSender:sender];
	
	NSMutableString	*tmp;
	NSURL			*url_ = nil;
	NSPasteboard	*pboard_ = [NSPasteboard generalPasteboard];
	NSArray			*types_;
	
	tmp = SGTemporaryString();
	
	[CMRThreadAttributes fillBuffer:tmp withThreadInfoForCopying:array_];
	url_ = [CMRThreadAttributes threadURLFromDictionary:[array_ lastObject]];
	
	types_ = [NSArray arrayWithObjects:NSURLPboardType, NSStringPboardType, nil];
	[pboard_ declareTypes:types_ owner:nil];
	
	[url_ writeToPasteboard:pboard_];
	[pboard_ setString:tmp forType:NSStringPboardType];
	
	[tmp deleteCharactersInRange:[tmp range]];
}

- (IBAction)chooseColumn:(id)sender
{
    NSString			*identifier_;
    NSTableColumn		*column_;
	ThreadsListTable	*tbView_;
	
	UTILAssertRespondsTo(sender, @selector(representedObject));
    
    identifier_ = [sender representedObject];
    UTILAssertKindOfClass(identifier_, NSString);
	
    tbView_ = self.threadListView;
    column_ = [tbView_ tableColumnWithIdentifier:identifier_];
	
	[tbView_ setColumnWithIdentifier:identifier_ visible:(column_ == nil)];
	
    [self saveBrowserListColumnState:tbView_];
	[self updateTableColumnsMenu];
}

- (IBAction)reloadThreadsList:(id)sender
{
	[_datasource startLoadingThreadsList:worker];
}
- (IBAction)showSelectedThread:(id)sender
{
	NSInteger rowIndex = [self.threadListView selectedRow];
	
	if (-1 == rowIndex) return;
	if ([self.threadListView numberOfSelectedRows] != 1) return;
	// #warning 64BIT: Check formatting arguments
	// 2010-03-28 tsawada2 修正済
	NSAssert2(
			  (rowIndex >= 0 && rowIndex < [self.threadListView numberOfRows]),
			  @"  rowIndex was over. size = %ld but was %ld",
			  (long)[self.threadListView numberOfRows],
			  (long)rowIndex);
	
	[NSApp sendAction:@selector(openSelectedThreads:) to:nil from:sender];
}
- (IBAction)selectThread:(id)sender
{
	// 特定のモディファイア・キーが押されているときは
	// クリックで項目を選択してもスレッドを読み込まない
	if ([[NSApp currentEvent] modifierFlags] & NSAlternateKeyMask) return;
	
	[self showSelectedThread:self];
}
/*
 NSTableView action, doubleAction はカラムのクリックでも
 発生するので、以下のメソッドでフックする。
 */
- (IBAction)tableViewActionDispatch:(id)sender actionKey:(NSString *)aKey defaultAction:(SEL)defaultAction
{
	SEL action_;
	
	// カラムのクリック
	if (-1 == [self.threadListView clickedRow]) return;
	
	// 設定されたアクションにディスパッチ
	action_ = SGTemplateSelector(aKey);
	if (NULL == action_ || _cmd == action_) {
		action_ = defaultAction;
	}
	[NSApp sendAction:action_ to:nil /*self*/ from:sender];
}

- (IBAction)listViewAction:(id)sender
{
    // 時間内に二度目のクリックが発生した場合は、ダブルクリックと判断し、クリックの action を実行しない
    NSTimeInterval interval = [NSEvent bs_doubleClickInterval];
    NSEvent *nextEvent = [[self.threadListView window] nextEventMatchingMask:NSLeftMouseUpMask
															  untilDate:[NSDate dateWithTimeIntervalSinceNow:interval]
																 inMode:NSEventTrackingRunLoopMode
																dequeue:NO];
    NSEventType type = [nextEvent type];
    if (NSLeftMouseUp == type) {
        return;
    }
	[self tableViewActionDispatch:sender
						actionKey:kThreadsListTableActionKey
					defaultAction:@selector(selectThread:)];
}

- (IBAction)listViewDoubleAction:(id)sender
{
	[self tableViewActionDispatch:sender
						actionKey:kThreadsListTableDoubleActionKey
					defaultAction:@selector(openSelectedThreads:)];
}

- (BOOL)validateAddFavoritesItem:(id)theItem
{
	return [[CMRFavoritesManager defaultManager] validateItem:theItem withThreadAttributes:[self targetThreadsForSender:theItem]];

}
- (BOOL)validateUserInterfaceItem:(id<NSValidatedUserInterfaceItem>)theItem
{
	SEL action = [theItem action];
	if(action == @selector(reloadThreadsList:)) {
		return [self.threadListView selectedRow] != -1;
	}
	if(action == @selector(copyThreadAttributes:)) {
		return [[self targetThreadsForSender:theItem] count] != 0;
	}
	if(action == @selector(addFavorites:)) {
		return [self validateAddFavoritesItem:theItem];
	}
	if(action == @selector(deleteThread:)) {
		return [[self targetThreadsForSender:theItem] count] != 0;
	}
	if(action == @selector(cleanupDatochiFiles:)) {
		return [_datasource isBoard] && (self.viewMode == BSThreadsListShowsLiveThreads) && !self.filterString;
	}
	if(action == @selector(rebuildThreadsList:)) {
		return [_datasource isBoard];
	}
	
	return YES;
}
- (BOOL)validateMenuItem:(NSMenuItem *)menuItem
{
	return [self validateUserInterfaceItem:menuItem];
}
@end


@implementation KMThreadList(ViewBuilding)
- (void)setupStatusColumnWithTableColumn:(NSTableColumn *)column
{
    NSImage            *statusImage_;
    NSImageCell        *imageCell_;
    
    statusImage_ = [NSImage imageAppNamed:STATUS_HEADER_IMAGE_NAME];
    imageCell_  = [[NSImageCell alloc] initImageCell:nil];
	
    [[column headerCell] setAlignment:NSCenterTextAlignment];
    [[column headerCell] setImage:statusImage_];
	
    [imageCell_ setImageAlignment:NSImageAlignCenter];
    [imageCell_ setImageScaling:NSScaleNone];
    [imageCell_ setImageFrameStyle:NSImageFrameNone];
    
    [column setDataCell:imageCell_];
    [imageCell_ release];
}

- (void)updateThreadEnergyColumn
{
    NSTableColumn *column = [self.threadListView initialColumnWithIdentifier:BSThreadEnergyKey];
    if (!column) {
        return;
    }
    if ([CMRPref energyUsesLevelIndicator]) {
		BSIkioiCell *cell = [[BSIkioiCell alloc] initWithLevelIndicatorStyle:NSRelevancyLevelIndicatorStyle];
		[cell setMaxValue:10.0];
		[cell setMinValue:0.0];
		[cell setEditable:NO];
		[column setDataCell:cell];
		[cell release];
    } else {
        CMRRightAlignedTextColumnCell *cell = [[CMRRightAlignedTextColumnCell alloc] initTextCell:@""];
		
        [cell setAlignment:NSRightTextAlignment];
        [cell setAllowsEditingTextAttributes:NO];
        [cell setBezeled:NO];
        [cell setBordered:NO];
        [cell setEditable:NO];
        [cell setScrollable:NO];
		
        [cell setWraps:YES];
        [cell setDrawsBackground:NO];
        [column setDataCell:cell];
        [cell release];
    }
    NSTableColumn *currentColumn = [self.threadListView tableColumnWithIdentifier:BSThreadEnergyKey];
    if (currentColumn) {
        NSRect rect = NSZeroRect;
        NSUInteger idx = [[self.threadListView tableColumns] indexOfObject:currentColumn];
        if (idx != NSNotFound) {
            rect = [self.threadListView rectOfColumn:idx];
        }
        if (!NSEqualRects(rect, NSZeroRect)) {
            [self.threadListView setNeedsDisplayInRect:rect];
        }
    }
}

- (void)setupTableColumn:(NSTableColumn *)column
{
    if ([CMRThreadStatusKey isEqualToString:[column identifier]]) {
        [self setupStatusColumnWithTableColumn:column];
        return;
    } else if ([BSThreadEnergyKey isEqualToString:[column identifier]] && [CMRPref energyUsesLevelIndicator]) {
		BSIkioiCell *cell = [[BSIkioiCell alloc] initWithLevelIndicatorStyle:NSRelevancyLevelIndicatorStyle];
		[cell setMaxValue:10.0];
		[cell setMinValue:0.0];
		[cell setEditable:NO];
		[column setDataCell:cell];
		[cell release];
        return;
    }
	
	Class	cellClass;
	id		newCell;
	id		dataCell;
	
	dataCell = [column dataCell];
	if ([dataCell alignment] == NSRightTextAlignment) {
		cellClass = [CMRRightAlignedTextColumnCell class];
	} else {
		cellClass = [CMRTextColumnCell class];
	}
	
	newCell = [[cellClass alloc] initTextCell:@""];
	[newCell setAttributesFromCell:dataCell];
	[newCell setWraps:YES];
	[newCell setDrawsBackground:NO];
	[column setDataCell:newCell];
	[newCell release];
}
- (NSTableColumn *)tableColumnWithPropertyListRep:(id)plistRep
{
    id column_ = [(id <CMRPropertyListCoding>)[NSTableColumn alloc] initWithPropertyListRepresentation:plistRep];
    [self setupTableColumn:column_];
    return [column_ autorelease];
}
- (void)updateMenuItemStatusForColumnsMenu:(NSMenu *)menu_
{
    NSEnumerator        *iter_;
    NSMenuItem          *rep_;
    
    iter_ = [[menu_ itemArray] objectEnumerator];
    while (rep_ = [iter_ nextObject]) {
        NSInteger state_;
		
        state_ = (-1 == [self.threadListView columnWithIdentifier:[rep_ representedObject]])
		? NSOffState
		: NSOnState;
		
        [rep_ setState:state_];
    }
}
- (void)createDefaultTableColumnsWithTableView:(NSTableView *)tableView
{
    NSEnumerator        *iter_;
    id                  rep_;
    
    iter_ = [[CMRAppDelegate defaultColumnsArray] objectEnumerator];
	
    while (rep_ = [iter_ nextObject]) {
        NSTableColumn        *column_;
        
        column_ = [self tableColumnWithPropertyListRep:rep_];
        if (!column_) continue;
		
        [tableView addTableColumn:column_];
    }
	
	[(ThreadsListTable *)tableView setInitialState];
}

//
- (void)updateThreadsListTableWithNeedingDisplay:(BOOL)display
{
	AppDefaults *pref = CMRPref;
	BOOL	dontDrawBgColor = [pref threadsListTableUsesAlternatingRowBgColors];
	
    [self.threadListView setRowHeight:[pref threadsListRowHeight]];
    [self.threadListView setFont:[pref threadsListFont]];
    
    [self.threadListView setUsesAlternatingRowBackgroundColors:dontDrawBgColor];
	
	if (!dontDrawBgColor) { // do draw bg color
		[self.threadListView setBackgroundColor:[pref threadsListBackgroundColor]];
	}
	
	[self.threadListView setGridStyleMask:([pref threadsListDrawsGrid] ? NSTableViewSolidVerticalGridLineMask : NSTableViewGridNone)];
	
	[self.threadListView setNeedsDisplay:display];
}
///
- (void)updateTableColumnsMenu
{
	[self updateMenuItemStatusForColumnsMenu:[[[CMRMainMenuManager defaultManager] browserListColumnsMenuItem] submenu]];
	[self updateMenuItemStatusForColumnsMenu:[[self.threadListView headerView] menu]];
}
- (void)setupBrowserContextualMenuLabelNames
{
    NSMenuItem *tmp = [[self menu] itemWithTag:kTLContMenuLabelMenuTag];
    [tmp setView:[BSLabelMenuItemHolder labelMenuItemView]];
    [(BSLabelMenuItemView *)[tmp view] setTarget:self.threadListView];
}    

///
- (void)setupThreadsListTable
{
	id	tmp2;
	id	tmp;
    
    [self createDefaultTableColumnsWithTableView:self.threadListView];
	
    tmp = SGTemplateResource(kThreadsListTableICSKey);
    UTILAssertRespondsTo(tmp, @selector(stringValue));
    [self.threadListView setIntercellSpacing:NSSizeFromString([tmp stringValue])];
	
	[self updateThreadsListTableWithNeedingDisplay:NO];
	
	tmp2 = [CMRPref threadsListTableColumnState];
	if (tmp2) {
		[self.threadListView restoreColumnState:tmp2];
	}
	
    [self.threadListView setTarget:self];
    [self.threadListView setDelegate:self];
	
    // dispatch in listViewAction:
    [self.threadListView setAction:@selector(listViewAction:)];
    [self.threadListView setDoubleAction:@selector(listViewDoubleAction:)];
	
	// Favorites Item's Drag & Drop operation support:
	[self.threadListView registerForDraggedTypes:[NSArray arrayWithObjects:BSFavoritesIndexSetPboardType, nil]];
	
	[self.threadListView setAutosaveTableColumns:NO];
    [self.threadListView setVerticalMotionCanBeginDrag:NO];
	
    // Menu and Contextual Menus
	[self.threadListView setMenu:[self menu]];
	[[self.threadListView headerView] setMenu:[(CMRAppDelegate *)[NSApp delegate] browserListColumnsMenuTemplate]];
	
	// Leopard
    [self.threadListView setAllowsTypeSelect:NO];
	
	[self updateTableColumnsMenu];
	[self setupBrowserContextualMenuLabelNames];
}

@end

@implementation KMThreadList(KM_NSTableViewDelegate)
static BOOL isOptionKeyDown(void)
{
    NSUInteger flag_ = [NSEvent modifierFlags];
    if (flag_ & NSAlternateKeyMask) {
        return YES;
    } else {
        return NO;
    }
}
- (void)tableView:(NSTableView *)tableView didClickTableColumn:(NSTableColumn *)tableColumn
{
    static BOOL hasOptionClicked = NO;
    BoardManager *bm = [BoardManager defaultManager];
    NSString *boardName = self.datasource.boardName;
	
    // Sort:
    // カラムヘッダをクリックしたとき、まず
    // -[NSObject(NSTableDataSource) tableView:sortDescriptorsDidChange:] が送られ、
    // その後で -[NSObject(NSTableViewDelegate) tableView:didClickTableColumn:] が送られる。
	
    // Sort:
    // Mac OS標準的ソート変更 (Finderのリスト表示参照)
    // ソートの向きは各カラムごとに保存されており、
    // ハイライトされているカラムヘッダがクリックされた時以外は、
    // 保存されている向きでソートされる。
    // 既にハイライトされているヘッダをクリックした場合は
    // 昇順／降順の切り替えと見なす。
	
    // Sort:
    // option キーを押しながらヘッダをクリックした場合は、変更後の設定を CMRPref に保存する（グローバルな設定の変更）。
    // ただし、option キーを押しながらクリックした場合、sortDescriptorDidChange: は呼ばれない。
    // それどころか、カラムのハイライトも更新されない。
    // 仕方がないので、option キーを押しながらクリックされた場合は、
    // ここでダミーのクリックイベントをもう一度発生させ、通常のカラムヘッダクリックをシミュレートする。
    // ダミーイベントによってもう一度 -tableView:didClickTableColumn: が発生するので、
    // そこで必要な処理を行なう。
    if (isOptionKeyDown()) {
        NSEvent *dummyEvent = [NSApp currentEvent];
        hasOptionClicked = YES;
        // このへん、Thousand のコード（THTableHeaderView.m）を参考にした
        NSEvent *downEvent = [NSEvent mouseEventWithType:NSLeftMouseDown
                                                location:[dummyEvent locationInWindow]
                                           modifierFlags:0
                                               timestamp:[dummyEvent timestamp]
                                            windowNumber:[dummyEvent windowNumber]
                                                 context:[dummyEvent context]
                                             eventNumber:[dummyEvent eventNumber]+1
                                              clickCount:1
                                                pressure:1.0];
        NSEvent *upEvent = [NSEvent mouseEventWithType:NSLeftMouseUp
                                              location:[dummyEvent locationInWindow]
                                         modifierFlags:0
                                             timestamp:[dummyEvent timestamp]
                                          windowNumber:[dummyEvent windowNumber]
                                               context:[dummyEvent context]
                                           eventNumber:[dummyEvent eventNumber]+2
                                            clickCount:1
                                              pressure:1.0];
        [NSApp postEvent:upEvent atStart:NO];
        [NSApp postEvent:downEvent atStart:YES];
		
        return;
    }
	
    // 設定の保存
    [bm setSortDescriptors:[tableView sortDescriptors] forBoard:boardName];
	
    if (hasOptionClicked) {
        [CMRPref setThreadsListSortDescriptors:[tableView sortDescriptors]];
        hasOptionClicked = NO;
    }
	
    NSInteger selected = [tableView selectedRow];
    if (selected != -1) {
        CMRAutoscrollCondition prefMask = [CMRPref threadsListAutoscrollMask];
        if (prefMask & CMRAutoscrollWhenTLSort) {
            [tableView scrollRowToVisible:selected];
        } else {
            [tableView scrollRowToVisible:0];
        }
    } else {
        [tableView scrollRowToVisible:0];
    }
	
    UTILDebugWrite(@"Catch tableView:didClickTableColumn:");
}

- (void)saveBrowserListColumnState:(NSTableView *)targetTableView
{
	if(self.threadListView != targetTableView) return;
    [CMRPref setThreadsListTableColumnState:[self.threadListView columnState]];
	/*    if ([[self splitView] isVertical]) {
	 NSScrollView *scrollView = [targetTableView enclosingScrollView];
	 if (!scrollView) {
	 return;
	 }
	 //        CGFloat tableViewWidth = [targetTableView frame].size.width;
	 //        CGFloat scrollViewWidth = [[scrollView contentView] frame].size.width;
	 //        [scrollView setHasHorizontalScroller:(tableViewWidth > scrollViewWidth)];        
	 }*/
}

- (void)tableViewColumnDidMove:(NSNotification *)aNotification
{
    [self saveBrowserListColumnState:[aNotification object]];
}

- (void)tableViewColumnDidResize:(NSNotification *)aNotification
{
    [self saveBrowserListColumnState:[aNotification object]];
}

@end


#import "BSModalStatusWindowController.h"
@implementation KMThreadList(RebuildDatabase)
- (void)showBrowserCriticalAlertMessage:(NSString *)messageTemplate informative:(NSString *)informativeText help:(NSString *)helpAnchor didEndSel:(SEL)didEndSelector
{
    NSAlert *alert = [[[NSAlert alloc] init] autorelease];
    [alert setAlertStyle:NSCriticalAlertStyle];
    [alert setMessageText:[NSString stringWithFormat:NSLocalizedStringFromTable(messageTemplate, @"ThreadsList", nil), [_datasource boardName]]];
    [alert setInformativeText:NSLocalizedStringFromTable(informativeText, @"ThreadsList", nil)];
    [alert addButtonWithTitle:NSLocalizedStringFromTable(@"DragDropTrashOK", @"ThreadsList", nil)];
    [alert addButtonWithTitle:NSLocalizedStringFromTable(@"DragDropTrashCancel", @"ThreadsList", nil)];
    [alert setShowsHelp:YES];
    [alert setHelpAnchor:NSLocalizedStringFromTable(helpAnchor, @"ThreadsList", nil)];
    [alert setDelegate:[NSApp delegate]];
    [alert beginSheetModalForWindow:[[self view] window]
                      modalDelegate:self
                     didEndSelector:didEndSelector
                        contextInfo:NULL];
}

- (void)cleanupDatochiFilesAlertDidEnd:(NSAlert *)alert returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo
{
    if (returnCode == NSAlertFirstButtonReturn) {
        [_datasource removeDatochiFiles];
    }
}

- (void)rebuildDoneAlertDidEnd:(NSAlert *)alert returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo
{
    if (returnCode == NSAlertSecondButtonReturn) {
        NSArray *files = (NSArray *)contextInfo;
        [[NSWorkspace sharedWorkspace] revealFilesInFinder:files];
        [files release];
    }
}

- (void)rebuildThreadsListAlertDidEnd:(NSAlert *)alert returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo
{
    if (returnCode != NSAlertFirstButtonReturn) {
        // Canceled. Nothing to do.
        return;
    }
	
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(rebuildingDidEnd:) name:CMRThreadsListDidChangeNotification object:_datasource];
	
    [[alert window] orderOut:nil];
	
    BSModalStatusWindowController *controller = [[BSModalStatusWindowController alloc] init];
    [[controller progressIndicator] setIndeterminate:YES];
    [[controller messageTextField] setStringValue:NSLocalizedString(@"Rebuild Database Msg", nil)];
    [[controller infoTextField] setStringValue:[_datasource boardName]];
	
    NSModalSession session = [NSApp beginModalSessionForWindow:[controller window]];
    [[controller progressIndicator] startAnimation:nil];
	
    [_datasource rebuildThreadsList];
    while (1) {
        if ([NSApp runModalSession:session] != NSRunContinuesResponse) {
            break;
        }
    }
    [[controller progressIndicator] stopAnimation:nil];
    [controller close];
    [controller release];
    [NSApp endModalSession:session];
	
    NSAlert *alert2;
    NSError *rebuildError = _datasource.rebuildError;
    NSArray *invalidFiles;
    if (rebuildError) {
        invalidFiles = [[NSArray alloc] initWithArray:[[rebuildError userInfo] objectForKey:DatabaseManagerInvalidFilePathsArrayKey]];
        alert2 = [NSAlert alertWithError:(rebuildError)];
        _datasource.rebuildError = nil;
    } else {
        invalidFiles = nil;
        alert2 = [[[NSAlert alloc] init] autorelease];
        [alert2 setAlertStyle:NSInformationalAlertStyle];
        [alert2 setMessageText:NSLocalizedStringFromTable(@"RebuildingEndAlert", @"ThreadsList", nil)];        
    }
	
    [alert2 beginSheetModalForWindow:[[self view] window]
                       modalDelegate:self
                      didEndSelector:@selector(rebuildDoneAlertDidEnd:returnCode:contextInfo:)
                         contextInfo:(invalidFiles ? (void *)invalidFiles : NULL)];
}

- (void)rebuildingDidEnd:(NSNotification *)notification
{
    [[NSNotificationCenter defaultCenter] removeObserver:self name:CMRThreadsListDidChangeNotification object:nil];
    [NSApp abortModal];
}
@end
