You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

1429 lines
46 KiB

/*
* Copyright (C) 2003 Ulf Erikson <ulferikson@fastmail.fm>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#ifdef __MINGW32__
# define _WIN32_IE 0x0500
# include <windows.h>
# include <commctrl.h>
# include "../missing.h"
#endif
#include "history.h"
#include "HistoryNode.h"
#include "../kmeleon_plugin.h"
#include "../KMeleonConst.h"
#include "../LocalesUtils.h"
extern Locale* gLoc;
// Include for global history
#include "nsCOMPtr.h"
#include "nsEmbedString.h"
#include "rdf.h"
#include "nsServiceManagerUtils.h"
#include "nsISimpleEnumerator.h"
#include "nsIRDFDataSource.h"
#include "nsIRDFService.h"
#include "prtime.h"
#include "nsMemory.h"
#include "../Utils.h"
#include <wininet.h>
#define VK_OEM_MINUS 0xBD
#define VK_OEM_PERIOD 0xBE
#define VK_MINUS VK_OEM_MINUS
#define VK_PERIOD VK_OEM_PERIOD
extern kmeleonPlugin kPlugin;
extern void * KMeleonWndProc;
static CHistoryNode gHistoryRoot("", HISTORY_FOLDER, 0);
static CHistoryNode *workingHist;
static CHistoryNode *freeNode;
static HWND hEditWnd;
#define SEARCH_LEN 256
static char str[SEARCH_LEN];
static int len;
static int pos;
static int circling;
static BOOL zoom = 0;
static BOOL maximized;
static int sortOrder;
#define CUT 1
#define KILL 1
#define PASTE 1
#define SORT_BY_DATE 0
#define SORT_BY_URL 1
#define SORT_BY_DATE_BACKWARDS 2
static void FillTree(HWND hTree, HTREEITEM parent, CHistoryNode &node, int level);
static void DeleteItem(HWND hTree, HTREEITEM item);
static void OnSize(int height, int width);
static void OnRClick(HWND hTree);
static inline CHistoryNode *GetHistoryNode(HWND hTree, HTREEITEM htItem) {
TVITEMEX tvItem;
tvItem.mask = TVIF_PARAM;
tvItem.hItem = htItem;
TreeView_GetItem(hTree, &tvItem);
return (CHistoryNode *)tvItem.lParam;
}
static inline void UnixTimeToSystemTime(time_t t, LPSYSTEMTIME pst) {
// Note that LONGLONG is a 64-bit value
LONGLONG ll;
FILETIME ft1;
FILETIME ft2;
#ifdef __MINGW32__
ll = Int32x32To64(t, 10000000) + 116444736000000000LL;
#else
ll = Int32x32To64(t, 10000000) + 116444736000000000L;
#endif
ft1.dwLowDateTime = (DWORD)ll;
ft1.dwHighDateTime = ll >> 32;
FileTimeToLocalFileTime(&ft1, &ft2);
FileTimeToSystemTime(&ft2, pst);
}
static int parseInt64(char *buf, LONGLONG *outValue)
{
int c, charsRead = 0;
LONGLONG value = 0;
while (buf && *buf) {
c = *buf;
if (c == 0 || !isdigit(c))
break;
++charsRead;
LONGLONG digit = c - '0';
value *= 10L;
value += digit;
}
if (!charsRead)
return -1;
*outValue = value;
return 0;
}
// Hook function for keyboard hook
LRESULT CALLBACK KeyboardProc(int nCode, WPARAM wParam, LPARAM lParam) {
if ( (nCode != HC_ACTION) || (lParam & 0x80000000) ) { // highest bit of lParam high == key being released
return CallNextHookEx(NULL, nCode, wParam, lParam);
}
BOOL fEatKeystroke = false;
BOOL fakedKey = false;
HWND hasFocus = GetFocus();
HWND hTree = GetDlgItem(hEditWnd, IDC_TREE_HOTLIST);
if (!(hasFocus == hTree ||
hasFocus == GetDlgItem(hEditWnd, IDC_URL) ||
hasFocus == GetDlgItem(hEditWnd, IDOK) ||
hasFocus == GetDlgItem(hEditWnd, IDCANCEL)))
return CallNextHookEx(NULL, nCode, wParam, lParam);
int searching = 0;
if (wParam == VK_ESCAPE) {
fEatKeystroke = true;
if (len == 0)
SendMessage(GetDlgItem(hEditWnd, IDCANCEL), BM_CLICK, 0, 0);
else
len = 0;
}
else if (hasFocus == hTree) {
HTREEITEM hItem = TreeView_GetSelection(hTree);
if (hItem) {
switch (wParam) {
case VK_UP:
if (GetKeyState(VK_MENU) & 0x80) { // what genius decided to call ALT "VK_MENU"???
fEatKeystroke = true;
HTREEITEM hItemSelect = TreeView_GetPrevSibling(hTree, hItem);
if (!hItemSelect) hItemSelect = TreeView_GetPrevVisible(hTree, hItem);
if (!hItemSelect) break;
TreeView_SelectItem(hTree, hItemSelect);
}
break;
case VK_DOWN:
if (GetKeyState(VK_MENU) & 0x80) {
fEatKeystroke = true;
HTREEITEM hItemSelect = TreeView_GetNextSibling(hTree, hItem);
if (!hItemSelect) hItemSelect = TreeView_GetNextSibling(hTree, TreeView_GetParent(hTree, hItem));
if (!hItemSelect) hItemSelect = TreeView_GetNextVisible(hTree, hItem);
if (!hItemSelect) break;
TreeView_SelectItem(hTree, hItemSelect);
}
break;
case VK_RETURN:
{
fEatKeystroke = true;
CHistoryNode *node = GetHistoryNode(hTree, hItem);
if (node->type == HISTORY_BOOKMARK) {
node->lastVisit = time(NULL);
if (GetKeyState(VK_CONTROL) & 0x80) {
kPlugin.kFuncs->NavigateTo(node->url.c_str(), OPEN_BACKGROUND, NULL);
}
else {
kPlugin.kFuncs->NavigateTo(node->url.c_str(), OPEN_NORMAL, NULL);
}
TreeView_SelectItem(hTree, hItem); // just to fire off a SELCHANGED notifier to update the status (last visited!)
PostMessage(hWndFront, WM_COMMAND, wm_deferbringtotop, (LPARAM)NULL);
}
else if (node->type == HISTORY_FOLDER) {
TreeView_Expand(hTree, hItem, TVE_TOGGLE);
}
}
break;
case VK_TAB:
{
if (len > 0) {
wParam = str[len-1];
wParam = toupper(wParam);
fakedKey = true;
goto search_again;
}
fEatKeystroke = true;
if (zoom)
break;
int idc_next = IDC_URL;
if (IsWindowEnabled(GetDlgItem(hEditWnd, IDC_URL)) == 0)
idc_next = IDOK;
if (GetKeyState(VK_SHIFT) & 0x80)
idc_next = IDCANCEL;
if (idc_next == IDCANCEL || idc_next == IDOK)
SendMessage(GetDlgItem(hEditWnd, idc_next), BM_SETSTYLE, BS_DEFPUSHBUTTON, TRUE);
SetFocus(GetDlgItem(hEditWnd, idc_next));
if (idc_next != IDCANCEL && idc_next != IDOK)
SendDlgItemMessage(hEditWnd, idc_next, EM_SETSEL, 0, -1); // select all
}
break;
case VK_DELETE:
// DeleteItem(hTree, hItem);
break;
case ' ':
{
CHistoryNode *node = GetHistoryNode(hTree, hItem);
if (node->type == HISTORY_FOLDER && len == 0) {
fEatKeystroke = true;
TreeView_Expand(hTree, hItem, TVE_TOGGLE);
break;
}
}
// Fall through!
default:
{
if (GetKeyState(VK_CONTROL) & 0x80) {
switch (wParam) {
case 'U':
len = 0;
break;
case 'G':
if (len > 0) {
wParam = str[len-1];
wParam = toupper(wParam);
fakedKey = true;
goto search_again;
}
break;
}
break;
}
else if (GetKeyState(VK_MENU) & 0x80) {
break;
}
else {
switch (wParam) {
case VK_F3:
if (len > 0) {
wParam = str[len-1];
wParam = toupper(wParam);
fakedKey = true;
goto search_again;
}
break;
}
}
if (wParam == VK_BACK && len > 0) {
circling = 0;
len--;
}
else if ( (wParam == ' ' ||
(wParam >= '0' && wParam <= '9') ||
(wParam >= 'A' && wParam <= 'Z')) &&
len < (SEARCH_LEN-1) ) {
str[len] = tolower(wParam);
len++;
}
else if ( (wParam == VK_MINUS) &&
len < (SEARCH_LEN-1) ) {
str[len] = '-';
len++;
}
else if ( wParam == VK_PERIOD &&
len < (SEARCH_LEN-1) ) {
str[len] = '.';
len++;
}
else
break;
search_again:
fEatKeystroke = true;
searching = 1;
str[len] = 0;
if (len > 0) {
CHistoryNode *node;
int mypos = 0;
int firstpos = -1;
int searchfrom = pos;
if (len == 1 && !fakedKey) {
HTREEITEM hItem = TreeView_GetSelection(hTree);
node = GetHistoryNode(hTree, hItem);
if (workingHist->Index(searchfrom, node) == -1)
searchfrom = pos;
else
pos = searchfrom;
}
int newpos = workingHist->Search(str, searchfrom, mypos, firstpos, &node);
if (fakedKey || newpos == -1 ||
(circling && len > 1 && str[len-1] == str[len-2])) {
if (fakedKey || (len > 1 && str[len-1] == str[len-2]))
searchfrom++;
if (!fakedKey)
len--;
str[len] = 0;
mypos = 0;
firstpos = -1;
if (len > 0) {
newpos = workingHist->Search(str, searchfrom, mypos, firstpos, &node);
if (!fakedKey && (newpos == pos || (len > 1 && str[len-1] == str[len-2]))) {
circling = 1;
while (len > 1 && str[len-1] == str[len-2])
len--;
str[len] = 0;
mypos = 0;
firstpos = -1;
newpos = workingHist->Search(str, searchfrom, mypos, firstpos, &node);
}
}
}
else
circling = 0;
if (newpos == -1 || len == 0) {
pos = 0;
len = 0;
str[len] = 0;
searching = 0;
}
else {
TCHAR tmp[SEARCH_LEN+64];
_stprintf(tmp, gLoc->GetString(IDS_FIND), str);
SetWindowText( hEditWnd, tmp );
HTREEITEM hItem = TreeView_GetRoot(hTree);
int i = 1;
while (hItem && i < newpos) {
HTREEITEM hTmp = TreeView_GetChild(hTree, hItem);
if (!hTmp)
hTmp = TreeView_GetNextSibling(hTree, hItem);
if (!hTmp) {
HTREEITEM hTmp2 = TreeView_GetParent(hTree, hItem);
while (!hTmp && hTmp2) {
hTmp = TreeView_GetNextSibling(hTree, hTmp2);
hTmp2 = TreeView_GetParent(hTree, hTmp2);
}
}
hItem = hTmp;
i++;
}
if (hItem) {
TreeView_SelectItem(hTree, hItem);
TreeView_EnsureVisible(hTree, hItem);
}
pos = newpos;
}
}
}
}
}
}
else if (hasFocus == GetDlgItem(hEditWnd, IDOK) && wParam == VK_RETURN) {
SendMessage(GetDlgItem(hEditWnd, IDOK), BM_CLICK, 0, 0);
}
else if (hasFocus == GetDlgItem(hEditWnd, IDCANCEL) && wParam == VK_RETURN) {
SendMessage(GetDlgItem(hEditWnd, IDCANCEL), BM_CLICK, 0, 0);
}
else if (hasFocus == GetDlgItem(hEditWnd, IDC_URL) && wParam == VK_RETURN) {
fEatKeystroke = true;
SetFocus(GetDlgItem(hEditWnd, IDC_TREE_HOTLIST));
}
else if (wParam == VK_TAB) {
int fields[] = {IDC_TREE_HOTLIST, IDC_URL,
IDOK, IDCANCEL, IDC_TREE_HOTLIST};
if (GetDlgItem(hEditWnd, IDOK) == hasFocus ||
GetDlgItem(hEditWnd, IDCANCEL) == hasFocus)
SendMessage(hasFocus, BM_SETSTYLE, BS_PUSHBUTTON, TRUE);
for (int i=1; fields[i]!=IDC_TREE_HOTLIST; i++) {
if (GetDlgItem(hEditWnd, fields[i]) == hasFocus) {
int idc_next = fields[i + ((GetKeyState(VK_SHIFT) & 0x80) ? -1 : 1)];
if (idc_next == IDC_TREE_HOTLIST ||
idc_next == IDOK || idc_next == IDCANCEL ||
IsWindowEnabled(GetDlgItem(hEditWnd, idc_next))) {
fEatKeystroke = true;
if (idc_next == IDOK || idc_next == IDCANCEL)
SendMessage(GetDlgItem(hEditWnd, idc_next), BM_SETSTYLE, BS_DEFPUSHBUTTON, TRUE);
SetFocus(GetDlgItem(hEditWnd, idc_next));
if (idc_next != IDC_TREE_HOTLIST &&
idc_next != IDOK && idc_next != IDCANCEL)
SendDlgItemMessage(hEditWnd, idc_next, EM_SETSEL, 0, -1); // select all
break;
}
else {
hasFocus = GetDlgItem(hEditWnd, idc_next);
i = 0;
continue;
}
}
}
}
if ((searching == 0 || len==0) &&
wParam != VK_SHIFT && wParam != VK_MENU && wParam != VK_CONTROL) {
len = 0;
pos = 0;
str[len] = 0;
SetWindowText( hEditWnd, gLoc->GetString(IDS_HISTORY) );
circling = 0;
}
return(fEatKeystroke ? 1 : CallNextHookEx(NULL, nCode, wParam, lParam));
}
/*
int ParseHistoryEntry(char **p, CHistoryNode &node)
{
char *q = NULL;
char *r1 = NULL;
char *r2 = NULL;
char szURL[INTERNET_MAX_URL_LENGTH] = {0};
time_t lastVisit=0;
if (*p && **p && (q = strchr(*p, ':')) != NULL) {
*q++ = 0;
// Ugly conversion from milliseconds to seconds..
if (q-(*p) > 4)
*(q-4) = 0;
lastVisit = atol(*p);
strncpy(szURL, q, INTERNET_MAX_URL_LENGTH);
szURL[INTERNET_MAX_URL_LENGTH-1] = 0;
CHistoryNode * newNode =
new CHistoryNode(szURL, NULL, HISTORY_BOOKMARK, lastVisit);
if (newNode) {
node.AddChild(newNode);
return 1;
}
}
return 0;
}
int ParseHistory(char **p, CHistoryNode &node)
{
int size = 0;
char *q1 = strchr(*p, '\r');
char *q2 = strchr(*p, '\n');
while (*p && **p) {
if (!q1 || (q2 && q2 < q1))
q1 = q2;
if (q1)
*q1++ = 0;
while (*p && **p && isspace(**p))
(*p)++;
if (*p && isdigit(**p))
size += ParseHistoryEntry(p, node);
if ( (*p = q1) != NULL ) {
q1 = strchr(*p, '\r');
q2 = strchr(*p, '\n');
}
}
*p = q1;
return size;
}
*/
int HistoryAttr(nsCOMPtr<nsIRDFNode> target, TCHAR **v)
{
nsCOMPtr<nsIRDFLiteral> aR = do_QueryInterface(target);
if (aR)
{
PRUnichar *name;
aR->GetValue(&name);
int len;
len = WideCharToMultiByte(CP_ACP, 0, name, -1, 0, 0, NULL, NULL);
char *s = new char[len +1];
len = WideCharToMultiByte(CP_ACP, 0, name, -1, s, len, NULL, NULL);
s[len] = 0;
*v = s;
nsMemory::Free(name);
return true;
}
else
{
*v = NULL;
return false;
}
}
int readHistory() {
nsresult rv;
unsigned int HistorySize = 0;
while (gHistoryRoot.child)
gHistoryRoot.RemoveChild();
while (gHistoryRoot.next)
gHistoryRoot.RemoveNext();
nsCOMPtr<nsIRDFDataSource> HistoryRdfDataSource;
nsCOMPtr<nsISupports> isupports;
nsCOMPtr<nsIRDFService> RDFService(do_GetService("@mozilla.org/rdf/rdf-service;1", &rv));
if (NS_FAILED(rv)) return false;
nsIRDFResource *arcChild;
nsIRDFResource *HistoryRoot;
nsIRDFResource *arcName;
nsIRDFResource *arcURL;
nsIRDFResource *arcDate;
rv = RDFService->GetResource(nsDependentCString("NC:HistoryRoot"),&HistoryRoot);
if (NS_FAILED(rv)) return false;
rv = RDFService->GetDataSourceBlocking("rdf:history",getter_AddRefs(HistoryRdfDataSource));
if (NS_FAILED(rv)) return false;
rv = RDFService->GetResource(nsCString("http://home.netscape.com/NC-rdf#child"), &arcChild);
if (NS_FAILED(rv)) return false;
rv = RDFService->GetResource(nsCString("http://home.netscape.com/NC-rdf#URL"), &arcURL);
if (NS_FAILED(rv)) return false;
rv = RDFService->GetResource(nsCString("http://home.netscape.com/NC-rdf#Name"), &arcName);
if (NS_FAILED(rv)) return false;
rv = RDFService->GetResource(nsCString("http://home.netscape.com/NC-rdf#Date"), &arcDate);
if (NS_FAILED(rv)) return false;
nsCOMPtr<nsISimpleEnumerator> targets;
rv = HistoryRdfDataSource->GetTargets(HistoryRoot,arcChild,true,getter_AddRefs(targets));
if (NS_FAILED(rv)) return false;
// Another way to count the number
// of history entries?
while ( (rv=targets->GetNext(getter_AddRefs(isupports))) == NS_OK)
HistorySize++;
// Should be NS_ERROR_FAILURE...
if (rv != NS_ERROR_UNEXPECTED) return false;
rv = HistoryRdfDataSource->GetTargets(HistoryRoot,arcChild,true,getter_AddRefs(targets));
if (NS_FAILED(rv)) return false;
for (unsigned int i=0; i < HistorySize; i++)
{
PRTime date;
TCHAR *name = NULL;
TCHAR *URL = NULL;
nsCOMPtr<nsISupports> isupports;
rv = targets->GetNext(getter_AddRefs(isupports));
if (NS_FAILED(rv)) break;
nsCOMPtr<nsIRDFResource> aTarget = do_QueryInterface(isupports);
nsCOMPtr<nsIRDFNode> target;
rv = HistoryRdfDataSource->GetTarget(aTarget,arcDate,true,getter_AddRefs(target));
if (NS_FAILED(rv)) break;
nsCOMPtr<nsIRDFDate> nDate;
nDate = do_QueryInterface(target);
if (nDate)
{
rv = nDate->GetValue(&date);
if (NS_FAILED(rv)) break;
}
rv = HistoryRdfDataSource->GetTarget(aTarget,arcName,true,getter_AddRefs(target));
if (NS_FAILED(rv)) break;
HistoryAttr(target, &name);
rv = HistoryRdfDataSource->GetTarget(aTarget,arcURL,true,getter_AddRefs(target));
if (NS_FAILED(rv)) {delete name;break;}
HistoryAttr(target, &URL);
time_t t = date / PR_USEC_PER_SEC;
CHistoryNode * newNode = new CHistoryNode(URL, name, HISTORY_BOOKMARK, t);
if (newNode) gHistoryRoot.AddChild(newNode);
delete name;
delete URL;
}
return true;
/*
// char szHistFile[MAX_PATH];
kPlugin.kFuncs->GetPreference(PREF_STRING, "kmeleon.general.profileDir", szHistFile, (char*)"");
strcat(szHistFile, "history.txt");
FILE *hFile = fopen(szHistFile, "r");
if (hFile){
long hFileSize = FileSize(hFile);
char *hFileBuffer = new char[hFileSize+1];
if (hFileBuffer){
fread(hFileBuffer, sizeof(char), hFileSize, hFile);
hFileBuffer[hFileSize] = 0;
char *szTmpBuf = hFileBuffer;
ret = ParseHistory(&szTmpBuf, gHistoryRoot);
delete [] hFileBuffer;
}
fclose(hFile);
}
return ret;*/
}
CHistoryNode *groupURLs(CHistoryNode *oldList)
{
if (!oldList)
return NULL;
CHistoryNode *newList;
CHistoryNode *tmp;
newList = new CHistoryNode(oldList->url.c_str(), oldList->name.c_str(), HISTORY_FOLDER, 0);
sortOrder = SORT_BY_URL;
oldList->flatten();
oldList->sort(sortOrder);
tmp = oldList->child;
while (tmp) {
int len;
char *str = (char*)tmp->url.c_str();
char *p = strchr(str, '/');
if (p && *(p+1) == '/')
p = strchr(p+2, '/');
if (p) {
len = p - str;
*p = 0;
}
else
len = strlen(str);
CHistoryNode *newFolder =
new CHistoryNode(str, str, HISTORY_FOLDER, 0);
newList->AddChild(newFolder);
if (p) {
*p = '/';
}
while (tmp && strncmp(str, tmp->url.c_str(), len) == 0) {
CHistoryNode *newNode = new CHistoryNode(tmp->url.c_str(), tmp->name.c_str(), HISTORY_BOOKMARK, tmp->lastVisit);
newFolder->AddChild(newNode);
tmp = tmp->next;
}
}
while (oldList->child)
oldList->RemoveChild();
while (oldList->next)
oldList->RemoveNext();
oldList->child = newList->child;
oldList->next = newList->next;
return newList;
}
struct tm subSec(struct tm t, int sec) {
time_t date = mktime(&t);
date -= sec;
struct tm *pt = localtime(&date);
return *pt;
}
struct tm subMin(struct tm t, int min) {
return subSec(t, 60*min);
}
struct tm subHour(struct tm t, int h) {
return subMin(t, 60*h);
}
struct tm subDay(struct tm t, int d) {
return subHour(t, 24*d);
}
struct tm subMonth(struct tm t, int m) {
t.tm_mon -= m;
while (t.tm_mon < 0) {
t.tm_mon += 12;
t.tm_year--;
}
time_t date = mktime(&t);
struct tm *pt = localtime(&date);
return *pt;
}
struct tm subYear(struct tm t, int y) {
struct tm newt = {0};
t.tm_year -= y;
time_t date = mktime(&t);
struct tm *pt = localtime(&date);
if (pt)
newt = *pt;
return newt;
}
CHistoryNode *groupDates(CHistoryNode *oldList)
{
const UINT weekday[] = {IDS_Sunday, IDS_Monday, IDS_Tuesday, IDS_Wednesday, IDS_Thursday, IDS_Friday, IDS_Saturday};
const UINT month[] = {IDS_January, IDS_February, IDS_March, IDS_April, IDS_May, IDS_June, IDS_July, IDS_August, IDS_September, IDS_October, IDS_November, IDS_December};
if (!oldList)
return NULL;
CHistoryNode *newList;
CHistoryNode *tmp;
newList = new CHistoryNode(oldList->url.c_str(), oldList->name.c_str(), HISTORY_FOLDER, 0);
sortOrder = SORT_BY_DATE;
oldList->flatten();
oldList->sort(SORT_BY_DATE_BACKWARDS);
tmp = oldList->child;
int pass = 0;
time_t date = time(NULL);
struct tm *pt = localtime(&date);
struct tm n = *pt;
while (tmp) {
char str[100];
strcpy(str, gLoc->GetString(IDS_ERROR));
pt = localtime(&date);
struct tm t = *pt;
switch (pass) {
case 0:
t.tm_sec = 0;
t.tm_min = 0;
strcpy(str, gLoc->GetString(IDS_LAST_HOUR));
break;
case 1:
t.tm_hour = 0;
strcpy(str, gLoc->GetString(IDS_TODAY));
break;
case 2:
case 3:
case 4:
t = subDay(t, 1);
strcpy(str, gLoc->GetString(weekday[t.tm_wday]));
break;
case 5:
if (t.tm_wday < n.tm_wday) {
t = subDay(t, t.tm_wday);
strcpy(str, gLoc->GetString(IDS_THIS_WEEK));
}
else {
t = subDay(t, t.tm_wday);
strcpy(str, gLoc->GetString(IDS_LAST_WEEK));
pass++;
}
break;
case 6:
t = subDay(t, 7);
strcpy(str, gLoc->GetString(IDS_LAST_WEEK));
break;
case 7:
t = subDay(t, 7);
strcpy(str, gLoc->GetString(IDS_2WEEKS_AGO));
break;
case 8:
t = subDay(t, 7);
strcpy(str, gLoc->GetString(IDS_3WEEKS_AGO));
break;
case 9:
if (t.tm_mday < n.tm_mday) {
t = subDay(t, (t.tm_mday-1));
strcpy(str, gLoc->GetString(month[t.tm_mon]));
}
else {
t = subDay(t, (t.tm_mday-1));
strcpy(str, gLoc->GetString(month[t.tm_mon]));
pass++;
}
break;
case 10:
case 11:
case 12:
case 13:
case 14:
case 15:
t = subMonth(t, 1);
strcpy(str, gLoc->GetString(month[t.tm_mon]));
break;
case 16:
if (t.tm_yday > n.tm_yday)
break;
t = subMonth(t, t.tm_mon);
itoa(t.tm_year, str, 10);
break;
default:
t = subYear(t, 1);
if (t.tm_year)
itoa(1900 + t.tm_year, str, 10);
else
strcpy(str, gLoc->GetString(IDS_NEVER));
break;
}
date = mktime(&t);
if (tmp->lastVisit >= date) {
CHistoryNode *newFolder = new CHistoryNode(str, str, HISTORY_FOLDER, 0);
while (tmp && (tmp->lastVisit > date)) {
CHistoryNode *newNode = new CHistoryNode(tmp->url.c_str(), tmp->name.c_str(), HISTORY_BOOKMARK, tmp->lastVisit);
newFolder->AddChild(newNode);
tmp = tmp->next;
}
CHistoryNode *tmpFolder = groupURLs(newFolder);
newList->AddChild(tmpFolder);
tmpFolder = tmpFolder->child;
while (tmpFolder) {
tmpFolder->sort(SORT_BY_DATE);
tmpFolder = tmpFolder->next;
}
newFolder->child = NULL;
newFolder->next = NULL;
delete newFolder;
}
pass++;
}
while (oldList->child)
oldList->RemoveChild();
while (oldList->next)
oldList->RemoveNext();
oldList->child = newList->child;
oldList->next = newList->next;
sortOrder = SORT_BY_DATE;
return newList;
}
int CALLBACK ViewProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
static int error;
static HHOOK hHook;
static HWND hTree;
switch (uMsg){
case WM_INITDIALOG:
{
if (!readHistory()) {
MessageBox(NULL, gLoc->GetString(IDS_LOAD_FAILED), gLoc->GetString(IDS_TITLE), MB_OK);
ghWndView = NULL;
error = 1;
PostMessage(GetDlgItem(hDlg, IDCANCEL), BM_CLICK, 0, 0);
EndDialog(hDlg, 0);
return 0;
}
error = 0;
hEditWnd = hDlg;
len = 0;
str[len] = 0;
pos = 0;
circling = 0;
freeNode = new CHistoryNode(_Tr("Delete"), _Tr("Delete"), HISTORY_FOLDER, 0);
HICON hIcon;
TCHAR szFullPath[MAX_PATH];
FindSkinFile(szFullPath, _T("history-view.ico"));
if (*szFullPath==0 || (hIcon = (HICON)LoadImage( NULL, szFullPath, IMAGE_ICON, 0,0, LR_DEFAULTSIZE | LR_LOADFROMFILE ))==NULL)
hIcon = (HICON)LoadImage( kPlugin.hDllInstance, MAKEINTRESOURCE(IDB_ICON), IMAGE_ICON, 0,0, LR_DEFAULTSIZE );
if (hIcon)
SendMessage(hDlg, WM_SETICON, ICON_BIG, (LPARAM) hIcon);
if (*szFullPath==0 || (hIcon = (HICON)LoadImage( NULL, szFullPath, IMAGE_ICON, 16,16, LR_LOADFROMFILE ))==NULL)
hIcon = (HICON)LoadImage( kPlugin.hDllInstance, MAKEINTRESOURCE(IDB_ICON), IMAGE_ICON, 16,16, 0 );
if (hIcon)
SendMessage(hDlg, WM_SETICON, ICON_SMALL, (LPARAM) hIcon);
hTree = GetDlgItem(hDlg, IDC_TREE_HOTLIST);
TreeView_SetImageList(hTree, gImagelist, TVSIL_NORMAL);
sortOrder = SORT_BY_DATE;
kPlugin.kFuncs->GetPreference(PREF_INT, PREFERENCE_VIEW_SORT_ORDER, &sortOrder, &sortOrder);
if (sortOrder == SORT_BY_DATE)
workingHist = groupDates(&gHistoryRoot);
else
workingHist = groupURLs(&gHistoryRoot);
TVINSERTSTRUCT tvis;
tvis.hParent = NULL;
tvis.hInsertAfter = NULL;
tvis.itemex.mask = TVIF_TEXT | TVIF_PARAM | TVIF_IMAGE | TVIF_SELECTEDIMAGE;
tvis.itemex.iImage = IMAGE_FOLDER_SPECIAL_CLOSED;
tvis.itemex.iSelectedImage = IMAGE_FOLDER_SPECIAL_OPEN;
tvis.itemex.pszText = (TCHAR*)_Tr("All URLs");
tvis.itemex.lParam = (long)&workingHist;
// HTREEITEM newItem = TreeView_InsertItem(hTree, &tvis);
FillTree(hTree, NULL, *workingHist, sortOrder == SORT_BY_URL);
TreeView_SelectItem(hTree, TreeView_GetRoot(hTree));
int dialogleft = 50, dialogtop = 50, dialogwidth = 500, dialogheight = 500;
kPlugin.kFuncs->GetPreference(PREF_INT, PREFERENCE_VIEW_DLG_LEFT, &dialogleft, &dialogleft);
kPlugin.kFuncs->GetPreference(PREF_INT, PREFERENCE_VIEW_DLG_TOP, &dialogtop, &dialogtop);
kPlugin.kFuncs->GetPreference(PREF_INT, PREFERENCE_VIEW_DLG_WIDTH, &dialogwidth, &dialogwidth);
kPlugin.kFuncs->GetPreference(PREF_INT, PREFERENCE_VIEW_DLG_HEIGHT, &dialogheight, &dialogheight);
SetWindowPos(hDlg, 0, dialogleft, dialogtop, dialogwidth, dialogheight, 0);
kPlugin.kFuncs->GetPreference(PREF_BOOL, PREFERENCE_VIEW_ZOOM, &zoom, &zoom);
kPlugin.kFuncs->GetPreference(PREF_BOOL, PREFERENCE_VIEW_MAX, &maximized, &maximized);
SetWindowPos(hDlg, 0, dialogleft, dialogtop, dialogwidth, dialogheight, 0);
if (maximized)
ShowWindow(hDlg, SW_MAXIMIZE);
else
ShowWindow(hDlg, SW_NORMAL);
hHook = SetWindowsHookEx(WH_KEYBOARD, KeyboardProc, NULL, GetCurrentThreadId());
RECT rect;
GetClientRect(hDlg, &rect);
OnSize(rect.bottom, rect.right);
TreeView_SelectItem(hTree, TreeView_GetRoot(hTree)); // just to fire off a SELCHANGED notifier to update the view (zoom)
if (sortOrder == SORT_BY_DATE)
TreeView_Expand(hTree, TreeView_GetRoot(hTree), TVE_EXPAND);
}
return false;
case WM_NOTIFY:
{
NMTREEVIEW *nmtv = (NMTREEVIEW *)lParam;
// Selection changed
if (nmtv->hdr.code == TVN_SELCHANGED){
// Put the new url/title into the box
CHistoryNode *newNode = (CHistoryNode *)nmtv->itemNew.lParam;
if (newNode == workingHist) {
// root and separators have nothing
SetDlgItemText(hDlg, IDC_URL, _T(""));
SetDlgItemText(hDlg, IDC_LAST_VISIT, _T(""));
EnableWindow(GetDlgItem(hDlg, IDC_STATIC_URL), false);
EnableWindow(GetDlgItem(hDlg, IDC_URL), false);
EnableWindow(GetDlgItem(hDlg, IDC_STATIC_VISITED), false);
EnableWindow(GetDlgItem(hDlg, IDC_STATIC_PROPERTIES), false);
return true; // a lazy-man's "else"
}
// everything else has at least title, added date, and properties in general
EnableWindow(GetDlgItem(hDlg, IDC_STATIC_PROPERTIES), true);
SYSTEMTIME st;
TCHAR pszTmp[1024];
TCHAR pszDate[900];
if (newNode->type == HISTORY_BOOKMARK) {
EnableWindow(GetDlgItem(hDlg, IDC_STATIC_URL), true);
EnableWindow(GetDlgItem(hDlg, IDC_URL), true);
SetDlgItemText(hDlg, IDC_URL, newNode->url.c_str());
EnableWindow(GetDlgItem(hDlg, IDC_STATIC_VISITED), true);
EnableWindow(GetDlgItem(hDlg, IDC_LAST_VISIT), true);
}
else {
SetDlgItemText(hDlg, IDC_URL, _T(""));
EnableWindow(GetDlgItem(hDlg, IDC_STATIC_URL), false);
EnableWindow(GetDlgItem(hDlg, IDC_URL), false);
EnableWindow(GetDlgItem(hDlg, IDC_STATIC_VISITED), false);
EnableWindow(GetDlgItem(hDlg, IDC_LAST_VISIT), false);
}
if (newNode->lastVisit) {
UnixTimeToSystemTime(newNode->lastVisit, &st);
GetDateFormat(GetThreadLocale(), DATE_SHORTDATE, &st, NULL, pszDate, 899);
_tcscpy(pszTmp, pszDate);
_tcscat(pszTmp, _T(" "));
GetTimeFormat(GetThreadLocale(), NULL, &st, NULL, pszDate, 899);
_tcscat(pszTmp, pszDate);
SetDlgItemText(hDlg, IDC_LAST_VISIT, pszTmp);
}
else {
SetDlgItemText(hDlg, IDC_LAST_VISIT, newNode->type == HISTORY_BOOKMARK ? _Tr("Never") : _T(""));
}
}
else if (nmtv->hdr.code == (UINT) NM_DBLCLK){
TVHITTESTINFO hti;
GetCursorPos(&hti.pt);
ScreenToClient(hTree, &hti.pt);
HTREEITEM hItem = TreeView_HitTest(hTree, &hti);
if (hItem){
CHistoryNode *node = GetHistoryNode(hTree, hItem);
if (node->type == HISTORY_BOOKMARK) {
node->lastVisit = time(NULL);
kPlugin.kFuncs->NavigateTo(node->url.c_str(), OPEN_NORMAL, NULL);
TreeView_SelectItem(hTree, hItem); // just to fire off a SELCHANGED notifier to update the status (last visited!)
PostMessage(hWndFront, WM_COMMAND, wm_deferbringtotop, (LPARAM)NULL);
return true;
}
}
}
// right click...
else if (nmtv->hdr.code == (UINT) NM_RCLICK){
OnRClick(hTree);
}
}
break;
case WM_SIZE:
{
if(wParam != SIZE_MINIMIZED) {
RECT rect;
GetClientRect(hDlg, &rect);
OnSize(rect.bottom, rect.right);
}
}
break;
case WM_GETMINMAXINFO:
LPMINMAXINFO lpminmaxinfo;
lpminmaxinfo=(LPMINMAXINFO)lParam;
lpminmaxinfo->ptMinTrackSize.x = 195;
lpminmaxinfo->ptMinTrackSize.y = 300;
break;
case WM_COMMAND:
{
if (HIWORD(wParam) == BN_CLICKED) {
WORD id = LOWORD(wParam);
switch(id){
case IDCANCEL:
if (error) {
error = 0;
ghWndView = NULL;
if (hWndFront)
PostMessage(hWndFront, WM_COMMAND, wm_deferbringtotop, (LPARAM)NULL);
DestroyWindow(hDlg);
break;
}
if (!zoom) {
ghWndView = NULL;
if (gHistoryRoot.child)
delete gHistoryRoot.child;
if (gHistoryRoot.next)
delete gHistoryRoot.next;
gHistoryRoot.child = NULL;
gHistoryRoot.next = NULL;
UnhookWindowsHookEx(hHook);
if (hWndFront)
PostMessage(hWndFront, WM_COMMAND, wm_deferbringtotop, (LPARAM)NULL);
DestroyIcon((HICON)SendMessage(hDlg, WM_GETICON, ICON_SMALL, 0));
DestroyIcon((HICON)SendMessage(hDlg, WM_GETICON, ICON_BIG, 0));
DestroyWindow(hDlg);
break;
}
// fall through!
case IDOK:
{
WINDOWPLACEMENT wp;
wp.length = sizeof(wp);
GetWindowPlacement(hDlg, &wp);
kPlugin.kFuncs->SetPreference(PREF_INT, PREFERENCE_VIEW_DLG_LEFT, &wp.rcNormalPosition.left, FALSE);
kPlugin.kFuncs->SetPreference(PREF_INT, PREFERENCE_VIEW_DLG_TOP, &wp.rcNormalPosition.top, FALSE);
int temp;
temp = wp.rcNormalPosition.right - wp.rcNormalPosition.left;
kPlugin.kFuncs->SetPreference(PREF_INT, PREFERENCE_VIEW_DLG_WIDTH, &temp, FALSE);
temp = wp.rcNormalPosition.bottom - wp.rcNormalPosition.top;
kPlugin.kFuncs->SetPreference(PREF_INT, PREFERENCE_VIEW_DLG_HEIGHT, &temp, FALSE);
kPlugin.kFuncs->SetPreference(PREF_BOOL, PREFERENCE_VIEW_ZOOM, &zoom, FALSE);
temp = (wp.showCmd == SW_SHOWMAXIMIZED);
kPlugin.kFuncs->SetPreference(PREF_BOOL, PREFERENCE_VIEW_MAX, &temp, FALSE);
kPlugin.kFuncs->SetPreference(PREF_INT, PREFERENCE_VIEW_SORT_ORDER, &sortOrder, FALSE);
ghWndView = NULL;
if (gHistoryRoot.child)
delete gHistoryRoot.child;
if (gHistoryRoot.next)
delete gHistoryRoot.next;
gHistoryRoot.child = NULL;
gHistoryRoot.next = NULL;
UnhookWindowsHookEx(hHook);
if (hWndFront)
PostMessage(hWndFront, WM_COMMAND, wm_deferbringtotop, (LPARAM)NULL);
DestroyIcon((HICON)SendMessage(hDlg, WM_GETICON, ICON_SMALL, 0));
DestroyIcon((HICON)SendMessage(hDlg, WM_GETICON, ICON_BIG, 0));
DestroyWindow(hDlg);
// Delete all selected history entries
if (freeNode && freeNode->child) {
// This is not likely to be implemented in a while..
MessageBox(NULL, _Tr("Deletion is not implemented"), _Tr("History"), MB_OK);
/*
freeNode->flatten();
CHistoryNode *ptr = freeNode->child;
while (ptr) {
MessageBox(NULL, ptr->url.c_str(), "Delete", MB_OK);
ptr = ptr->next;
}
*/
}
if (freeNode)
delete freeNode;
break;
}
}
}
}
break;
}
return false;
}
static void FillTree(HWND hTree, HTREEITEM parent, CHistoryNode &node, int level)
{
TVINSERTSTRUCT tvis;
tvis.hParent = parent;
tvis.hInsertAfter = NULL;
tvis.itemex.mask = TVIF_TEXT | TVIF_PARAM | TVIF_IMAGE | TVIF_SELECTEDIMAGE;
tvis.itemex.iImage = IMAGE_BLANK;
tvis.itemex.iSelectedImage = IMAGE_BLANK;
int type;
CHistoryNode *child;
for (child=node.child; child; child=child->next) {
tvis.itemex.lParam = (long)child;
type = child->type;
if (type == HISTORY_FOLDER) {
tvis.itemex.iImage = level == 0 ? IMAGE_FOLDER_SPECIAL_CLOSED : IMAGE_FOLDER_CLOSED;
tvis.itemex.iSelectedImage = level == 0 ? IMAGE_FOLDER_SPECIAL_OPEN : IMAGE_FOLDER_OPEN;
tvis.itemex.pszText = (char *)child->name.c_str();
HTREEITEM thisItem = TreeView_InsertItem(hTree, &tvis);
FillTree(hTree, thisItem, *child, level+1);
}
else if (type == HISTORY_SEPARATOR) {
tvis.itemex.iImage = IMAGE_BLANK;
tvis.itemex.iSelectedImage = IMAGE_BLANK;
tvis.itemex.pszText = _T("---");
TreeView_InsertItem(hTree, &tvis);
}
else {
tvis.itemex.iImage = IMAGE_BOOKMARK;
tvis.itemex.iSelectedImage = IMAGE_BOOKMARK;
tvis.itemex.pszText = (char *)child->name.c_str();
TreeView_InsertItem(hTree, &tvis);
}
}
}
static void DeleteItem(HWND hTree, HTREEITEM item) {
CHistoryNode *node, *parentNode;
node = GetHistoryNode(hTree, item);
HTREEITEM parent = TreeView_GetParent(hTree, item);
if (parent){
parentNode = GetHistoryNode(hTree, parent);
}
else{
parentNode = workingHist;
}
if (freeNode && parentNode && parentNode->UnlinkNode(node))
freeNode->AddChild(node);
// select a new item
HTREEITEM hSelect = TreeView_GetNextSibling(hTree, item);
if (!hSelect) hSelect = TreeView_GetPrevSibling(hTree, item);
if (!hSelect) hSelect = TreeView_GetParent(hTree, item);
TreeView_DeleteItem(hTree, item);
// must call selectitem after deleteitem, otherwise the SELCHANGED handler will try to update the deleted node!
TreeView_SelectItem(hTree, hSelect);
}
static void OnRClick(HWND hTree)
{
POINT mouse;
GetCursorPos(&mouse);
TVHITTESTINFO hti;
hti.pt.x = mouse.x;
hti.pt.y = mouse.y;
ScreenToClient(hTree, &hti.pt);
HTREEITEM hItem = TreeView_HitTest(hTree, &hti);
if (hItem) {
TreeView_SelectItem(hTree, hItem);
HMENU topMenu = gLoc->LoadMenu(IDR_CONTEXTMENU);
HMENU contextMenu = GetSubMenu(topMenu, 0);
CheckMenuItem(contextMenu, ID__SORT_DATE, MF_BYCOMMAND | MF_UNCHECKED);
CheckMenuItem(contextMenu, ID__SORT_URL, MF_BYCOMMAND | MF_UNCHECKED);
if (sortOrder == SORT_BY_DATE)
CheckMenuItem(contextMenu, ID__SORT_DATE, MF_BYCOMMAND | MF_CHECKED);
if (sortOrder == SORT_BY_URL)
CheckMenuItem(contextMenu, ID__SORT_URL, MF_BYCOMMAND | MF_CHECKED);
if (zoom)
CheckMenuItem(contextMenu, ID__ZOOM, MF_BYCOMMAND | MF_UNCHECKED);
else
CheckMenuItem(contextMenu, ID__ZOOM, MF_BYCOMMAND | MF_CHECKED);
int command = TrackPopupMenu(contextMenu, TPM_RIGHTBUTTON | TPM_LEFTALIGN | TPM_RETURNCMD, mouse.x, mouse.y, 0, hTree, NULL);
DestroyMenu(topMenu);
switch (command) {
case ID__OPEN:
{
CHistoryNode *node = GetHistoryNode(hTree, hItem);
if (node->type == HISTORY_BOOKMARK) {
kPlugin.kFuncs->NavigateTo(node->url.c_str(), OPEN_NORMAL, NULL);
TreeView_SelectItem(hTree, hItem); // just to fire off a SELCHANGED notifier to update the status (last visited!)
PostMessage(hWndFront, WM_COMMAND, wm_deferbringtotop, (LPARAM)NULL);
}
}
break;
case ID__OPEN_BACKGROUND:
{
CHistoryNode *node = GetHistoryNode(hTree, hItem);
if (node->type == HISTORY_BOOKMARK) {
kPlugin.kFuncs->NavigateTo(node->url.c_str(), OPEN_BACKGROUND, NULL);
TreeView_SelectItem(hTree, hItem); // just to fire off a SELCHANGED notifier to update the status (last visited!)
}
}
break;
case ID__BOOKMARK_DELETE:
DeleteItem(hTree, hItem);
break;
break;
case ID__SORT_URL:
case ID__SORT_DATE:
{
if ((command == ID__SORT_URL && sortOrder == SORT_BY_URL) ||
(command == ID__SORT_DATE && sortOrder == SORT_BY_DATE)) {
break;
}
// TreeView_DeleteAllItems(hTree);
TreeView_SelectItem(hTree, TreeView_GetRoot(hTree));
HTREEITEM hTmp = TreeView_GetLastVisible(hTree);
while (hTmp) {
TreeView_DeleteItem(hTree, hTmp);
hTmp = TreeView_GetLastVisible(hTree);
}
if (command == ID__SORT_URL) {
workingHist = groupURLs(&gHistoryRoot);
}
else if (command == ID__SORT_DATE) {
workingHist = groupDates(&gHistoryRoot);
}
FillTree(hTree, NULL, *workingHist, command == ID__SORT_URL);
TreeView_SelectItem(hTree, TreeView_GetRoot(hTree));
if (command == ID__SORT_DATE)
TreeView_Expand(hTree, TreeView_GetRoot(hTree), TVE_EXPAND);
break;
}
case ID__ZOOM:
{
zoom = !zoom;
RECT rect;
GetClientRect(hEditWnd, &rect);
OnSize(rect.bottom, rect.right);
TreeView_SelectItem(hTree, hItem); // just to fire off a SELCHANGED notifier
}
break;
}
}
}
// this is ugly, see... all this need to be changed each time the dialog is re-arranged...
#define BORDER 8 // this one is pixels, the rest are DLUs
#define BUTTON_HEIGHT 15
#define OK_WIDTH 45
#define CANCEL_WIDTH 45
#define PROPERTIES_HEIGHT 50
#define EDITBOXES_LEFT 36
#define EDITBOXES_TOP (276-174)
#define EDITBOXES_HEIGHT 12
#define DATES_WIDTH 78
#define OTHER_WIDTH 35
#define convX(x) MulDiv((int)(x), buX, 4)
#define convY(y) MulDiv((int)(y), buY, 8)
static void OnSize(int height, int width) {
int buX, buY;
RECT rc; // GetDialogBaseUnits returns incorrect values...?
SetRect(&rc, 0, 0, 4, 8);
MapDialogRect(hEditWnd, &rc);
buY = rc.bottom;
buX = rc.right;
// resize tree
SetWindowPos(GetDlgItem(hEditWnd, IDC_TREE_HOTLIST), 0,
zoom ? 0 : BORDER,
zoom ? 0 : BORDER,
zoom ? width : width-(BORDER*2),
zoom ? height : height-BORDER*4-convY(BUTTON_HEIGHT+PROPERTIES_HEIGHT),
0);
// move cancel button
SetWindowPos(GetDlgItem(hEditWnd, IDCANCEL), 0,
width-BORDER-convX(CANCEL_WIDTH),
zoom ? height + BORDER : height-BORDER-convY(BUTTON_HEIGHT),
0, 0, SWP_NOSIZE);
// move ok button
SetWindowPos(GetDlgItem(hEditWnd, IDOK), 0,
width-BORDER*2-convX(CANCEL_WIDTH+OK_WIDTH),
zoom ? height + BORDER : height-BORDER-convY(BUTTON_HEIGHT),
0, 0, SWP_NOSIZE);
// move/resize properties box
SetWindowPos(GetDlgItem(hEditWnd, IDC_STATIC_PROPERTIES), 0,
BORDER,
zoom ? height + BORDER : height-BORDER*2-convY(BUTTON_HEIGHT+PROPERTIES_HEIGHT),
width-BORDER*2,
convY(PROPERTIES_HEIGHT),
0);
// move/resize properties widgets
int x_max = convX(EDITBOXES_LEFT+DATES_WIDTH)+BORDER;
int x_half = width/2 + BORDER/2;
int x2 = min(x_max, x_half);
int w1 = x2 - convX(EDITBOXES_LEFT) - BORDER;
int w2 = width - x2 - 2*BORDER;
SetWindowPos(GetDlgItem(hEditWnd, IDC_STATIC_VISITED), 0,
BORDER*2,
zoom ? height + BORDER : height-convY(EDITBOXES_TOP-EDITBOXES_HEIGHT*5.0),
0,
0, SWP_NOSIZE);
SetWindowPos(GetDlgItem(hEditWnd, IDC_LAST_VISIT), 0,
convX(EDITBOXES_LEFT)+5,
zoom ? height + BORDER : height-convY(EDITBOXES_TOP-EDITBOXES_HEIGHT*5.0),
w1, // convX(DATES_WIDTH),
convY(EDITBOXES_HEIGHT),
0);
SetWindowPos(GetDlgItem(hEditWnd, IDC_STATIC_URL), 0,
BORDER*2,
zoom ? height + BORDER : height-convY(EDITBOXES_TOP-EDITBOXES_HEIGHT*3.75),
0, 0, SWP_NOSIZE);
SetWindowPos(GetDlgItem(hEditWnd, IDC_URL), 0,
convX(EDITBOXES_LEFT),
zoom ? height + BORDER : height-convY(EDITBOXES_TOP-EDITBOXES_HEIGHT*3.75)-2,
width-convX(EDITBOXES_LEFT)-BORDER*2,
convY(EDITBOXES_HEIGHT),
0);
InvalidateRgn(hEditWnd, NULL, FALSE);
UpdateWindow(hEditWnd);
}